List of lists changes reflected across sublists unexpectedly
我需要在python中创建一个列表,所以我键入了以下内容:
1 | myList = [[1] * 4] * 3 |
列表如下:
1 | [[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]] |
然后我改变了最里面的一个值:
1 | myList[0][0] = 5 |
现在我的列表如下:
1 | [[5, 1, 1, 1], [5, 1, 1, 1], [5, 1, 1, 1]] |
这不是我想要或期望的。有人能解释一下发生了什么事,以及如何避开它吗?
当你写
要修复它,您需要确保在每个位置创建一个新列表。一种方法是
1 | [[1]*4 for _ in range(3)] |
它将每次重新评估
你可能想知道为什么
相反,列表理解会在每次迭代中重新评估元素表达式。
顺便说一下,
1 2 3 | size = 3 matrix_surprise = [[0] * size] * size matrix = [[0]*size for i in range(size)] |
实时python教程可视化
实际上,这正是你所期望的。让我们来分解这里发生的事情:
你写
1 | lst = [[1] * 4] * 3 |
这相当于:
1 2 | lst1 = [1]*4 lst = [lst1]*3 |
这意味着
1 2 | lst[0][0] = 5 lst1[0] = 5 |
因为
要获得所需的行为,可以使用列表理解:
1 | lst = [ [1]*4 for n in xrange(3) ] |
在这种情况下,将为每个n重新计算表达式,从而得到不同的列表。
1 | [[1] * 4] * 3 |
甚至:
1 | [[1, 1, 1, 1]] * 3 |
创建一个引用内部
与本例相同:
1 2 3 4 5 6 7 | >>> inner = [1,1,1,1] >>> outer = [inner]*3 >>> outer [[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]] >>> inner[0] = 5 >>> outer [[5, 1, 1, 1], [5, 1, 1, 1], [5, 1, 1, 1]] |
在那里可能不那么令人惊讶。
除了正确解释问题的公认答案外,在您的列表理解中,如果您使用的是python-2.x,那么使用
1 | [[1]*4 for _ in xrange(3)] # and in python3 [[1]*4 for _ in range(3)] |
此外,作为一种更为pythonic的方法,您可以使用
1 2 3 4 5 | >>> a=list(repeat(1,4)) [1, 1, 1, 1] >>> a[0]=5 >>> a [5, 1, 1, 1] |
p.s.使用numpy,如果您只想创建一个1或0的数组,您可以使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | In [1]: import numpy as np In [2]: In [2]: np.ones(4) Out[2]: array([ 1., 1., 1., 1.]) In [3]: np.ones((4, 2)) Out[3]: array([[ 1., 1.], [ 1., 1.], [ 1., 1.], [ 1., 1.]]) In [4]: np.zeros((4, 2)) Out[4]: array([[ 0., 0.], [ 0., 0.], [ 0., 0.], [ 0., 0.]]) In [5]: np.repeat([7], 10) Out[5]: array([7, 7, 7, 7, 7, 7, 7, 7, 7, 7]) |
简单地说,这是因为在Python中,所有东西都是通过引用工作的,所以当您以这种方式创建列表时,您基本上会遇到这样的问题。
要解决您的问题,您可以执行以下任一操作:1。对numpy.empty使用numpy数组文档2。在到达列表时附加该列表。三。如果你想的话,你也可以用字典。
1 | myList = [[1]*4 for _ in range(3)] |
或
1 | myList = [[1 for __ in range(4)] for _ in range(3)] |
这里需要注意的重要一点是,
python容器包含对其他对象的引用。请参见此示例:
1 2 3 4 5 6 7 | >>> a = [] >>> b = [a] >>> b [[]] >>> a.append(1) >>> b [[1]] |
在这个目录中,
一个列表乘以一个整数等于将该列表多次添加到其自身中(请参见公共序列操作)。所以继续这个例子:
1 2 3 4 5 6 7 | >>> c = b + b >>> c [[1], [1]] >>> >>> a[0] = 2 >>> c [[2], [2]] |
我们可以看到,列表
python faq还包含对这种行为的解释:如何创建多维列表?
试图用更具描述性的方式来解释,
操作1:
1 2 3 4 5 6 | x = [[0, 0], [0, 0]] print(type(x)) # <class 'list'> print(x) # [[0, 0], [0, 0]] x[0][0] = 1 print(x) # [[1, 0], [0, 0]] |
操作2:
1 2 3 4 5 6 | y = [[0] * 2] * 2 print(type(y)) # <class 'list'> print(y) # [[0, 0], [0, 0]] y[0][0] = 1 print(y) # [[1, 0], [1, 0]] |
注意到为什么不修改第一个列表的第一个元素而不修改每个列表的第二个元素?这是因为
如果要创建克隆副本,请尝试操作3:
1 2 3 4 5 6 7 8 9 | import copy y = [0] * 2 print(y) # [0, 0] y = [y, copy.deepcopy(y)] print(y) # [[0, 0], [0, 0]] y[0][0] = 1 print(y) # [[1, 0], [0, 0]] |
另一种创建克隆副本的有趣方法,操作4:
1 2 3 4 5 6 7 8 9 | import copy y = [0] * 2 print(y) # [0, 0] y = [copy.deepcopy(y) for num in range(1,5)] print(y) # [[0, 0], [0, 0], [0, 0], [0, 0]] y[0][0] = 5 print(y) # [[5, 0], [0, 0], [0, 0], [0, 0]] |
我想每个人都能解释发生了什么。我建议一种解决方法:
1 | myList[0][0] = 5 |
然后你有:
1 | [[5, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]] |
让我们用以下方式重写您的代码:
1 2 3 4 5 | x = 1 y = [x] z = y * 4 myList = [z] * 3 |
然后,运行下面的代码,使一切更加清晰。代码所做的基本上是打印所获得对象的
Return the"identity" of an object
帮助我们识别它们并分析发生的情况:
1 2 3 4 5 | print("myList:") for i, subList in enumerate(myList): print("\t[{}]: {}".format(i, id(subList))) for j, elem in enumerate(subList): print("\t\t[{}]: {}".format(j, id(elem))) |
您将得到以下输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | x: 1 y: [1] z: [1, 1, 1, 1] myList: [0]: 4300763792 [0]: 4298171528 [1]: 4298171528 [2]: 4298171528 [3]: 4298171528 [1]: 4300763792 [0]: 4298171528 [1]: 4298171528 [2]: 4298171528 [3]: 4298171528 [2]: 4300763792 [0]: 4298171528 [1]: 4298171528 [2]: 4298171528 [3]: 4298171528 |
现在让我们一步一步走。您有
通过使用内置列表函数,您可以这样做
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | a out:[[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]] #Displaying the list a.remove(a[0]) out:[[1, 1, 1, 1], [1, 1, 1, 1]] # Removed the first element of the list in which you want altered number a.append([5,1,1,1]) out:[[1, 1, 1, 1], [1, 1, 1, 1], [5, 1, 1, 1]] # append the element in the list but the appended element as you can see is appended in last but you want that in starting a.reverse() out:[[5, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]] #So at last reverse the whole list to get the desired list |