列表名单意外地反映在子列表上

我需要在Python中创build一个列表,所以我input以下内容:

myList = [[1] * 4] * 3 

清单看起来像这样:

 [[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]] 

然后我改变了最内在的一个值:

 myList[0][0] = 5 

现在我的列表如下所示:

 [[5, 1, 1, 1], [5, 1, 1, 1], [5, 1, 1, 1]] 

这不是我想要或预期的。 有人可以解释一下怎么回事,以及如何解决这个问题?

当你写[x]*3你会得到列表[x, x, x] 。 也就是说,一个列表有3个引用相同的x 。 当你修改这个单独的x ,通过它的所有三个引用就可以看到它。

要解决它,你需要确保你在每个位置创build一个新的列表。 一种方法是

 [[1]*4 for n in range(3)] 

每次重新评估[1]*4而不是一次评估一次,3次参考1个列表。


你可能想知道为什么*不能像列表理解那样制作独立的对象。 这是因为乘法运算符*在对象上运行,而没有看到expression式。 当您使用*[[1] * 4]乘以3时, *只能看到[[1] * 4]计算的1元素列表,而不是[[1] * 4expression式文本。 *不知道如何复制该元素,不知道如何重新评估[[1] * 4] ,甚至不知道你甚至想要复制,一般情况下,甚至没有办法复制元素。

*唯一的select是对现有的子列表进行新的引用,而不是尝试创build新的子列表。 其他任何东西都不一致,或者需要重新devise基本语言devise决策。

相比之下,列表理解重新评估每次迭代的元素expression式。 [[1] * 4 for n in range(3)] [1] * 4每次出于同样的原因重新评估[1] * 4 [x**2 for x in range(3)] x**2 [x**2 for x in range(3)]每次重新评估x**2 。 每个[1] * 4评估生成一个新的列表,所以列表理解做你想要的。

顺便说一下, [1] * 4也不会复制[1]的元素,但这并不重要,因为整数是不可变的。 你不能做1.value = 2 ,把1变成2。

 size = 3 matrix_surprise = [[0] * size] * size matrix = [[0]*size for i in range(size)] 

框架和对象

活的Python教师可视化

其实,这正是你所期望的。 让我们分解这里发生的事情:

你写

 lst = [[1] * 4] * 3 

这相当于:

 lst1 = [1]*4 lst = [lst1]*3 

这意味着lst是一个3个元素都指向lst1 。 这意味着以下两行是等同的:

 lst[0][0] = 5 lst1[0] = 5 

因为lst[0]不过是lst1

要获得所需的行为,可以使用列表理解:

 lst = [ [1]*4 for n in xrange(3) ] 

在这种情况下,对每个n重新评估expression式,导致不同的列表。

 [[1] * 4] * 3 

甚至:

 [[1, 1, 1, 1]] * 3 

创build一个引用内部[1,1,1,1] 3次的列表 – 不是内部列表的三个副本,所以无论何时修改列表(在任何位置),都会看到更改三次。

这个例子是一样的:

 >>> 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,使用xrange()返回一个更高效的生成器( range()在python 3中做同样的工作)一次性variablesn

 [[1]*4 for _ in xrange(3)] # and in python3 [[1]*4 for _ in range(3)] 

另外,作为一种更为Python的方式,你可以使用itertools.repeat()来创build一个重复元素的迭代器对象:

 >>> a=list(repeat(1,4)) [1, 1, 1, 1] >>> a[0]=5 >>> a [5, 1, 1, 1] 

PS使用numpy,如果你只想创build一个1或0的数组,你可以使用np.onesnp.zeros和/或其他数字使用np.repeat()

 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中,所有的工作都是通过引用的方式进行的 ,所以当你创build一个list列表的时候,你基本上就会遇到这样的问题。

要解决您的问题,您可以执行其中任何一个:1.使用numpy数组文档numpy.empty 2.追加到列表中的列表。 你也可以使用字典,如果你想

myList = [[1]*4] * 3在内存中创build一个列表对象[1,1,1,1] ,并将其引用复制3次。 这相当于obj = [1,1,1,1]; myList = [obj]*3 obj = [1,1,1,1]; myList = [obj]*3 。 对obj任何修改都会反映在三个地方,无论obj在列表中被引用。 正确的说法是:

 myList = [[1]*4 for _ in range(3)] 

要么

 myList = [[1 for __ in range(4)] for _ in range(3)] 

这里要注意的重点*运算符主要用于创build文字列表 。 由于1是一个文字,因此obj =[1]*4将创build[1,1,1,1] ,其中每个1是primefaces的,而不是 1重复的参考4次。 这意味着如果我们做obj[2]=42 ,那么obj将会像有些人所假设的那样变成[1,1,42,1] 而不是 [42,42,42,42]

让我们以下面的方式重写你的代码:

 x = 1 y = [x] z = y * 4 myList = [z] * 3 

然后,运行下面的代码,使一切更清晰。 代码所做的基本上是打印获得的对象的id

返回一个对象的“身份”

并会帮助我们识别它们并分析发生的事情:

 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))) 

你会得到以下输出:

 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 

所以现在让我们一步一步来。 你有x1 ,单个元素列表y包含x 。 你的第一步是y * 4 ,它会给你一个新的列表z ,它基本上是[x, x, x, x] ,即它创build一个新的列表,它有4个元素,它们是对初始x对象的引用。 净步骤非常相似。 你基本上做z * 3 ,它是[[x, x, x, x]] * 3并且返回[[x, x, x, x], [x, x, x, x], [x, x, x, x]] ,原因与第一步相同。

Python容器包含对其他对象的引用。 看到这个例子:

 >>> a = [] >>> b = [a] >>> b [[]] >>> a.append(1) >>> b [[1]] 

在这个b是一个列表,其中包含一个对列表a的引用。 列表a是可变的。

列表乘以整数相当于多次将列表添加到自身(请参阅常见的序列操作 )。 所以继续这个例子:

 >>> c = b + b >>> c [[1], [1]] >>> >>> a[0] = 2 >>> c [[2], [2]] 

我们可以看到,列表c现在包含两个引用来列出a ,它相当于c = b * 2

Python常见问题也包含这种行为的解释: 如何创build一个多维列表?

我想每个人都解释发生了什么事。 我build议一种解决方法:

myList = [[1 for i in range(4)] for j in range(3)]

 myList[0][0] = 5 

print myList

然后你有:

 [[5, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]] 

通过使用内置的列表function,你可以这样做

 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 

试图更具描述性地解释它,

操作1:

 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:

 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]] 

注意为什么不修改第一个列表的第一个元素不修改每个列表的第二个元素? 这是因为[0] * 2实际上是两个数字的列表,并且对0的引用不能被修改。

如果您要创build克隆副本,请尝试操作3:

 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]] 

另一个有趣的方法来创build克隆副本,操作4:

 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, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]] 

如果你写myList[0][0] = 5输出会是;

 >>> [[5, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]] >>> 

如你所料。 但是既然你像这样定义你的列表variables,

 [[1] * 4] * 3 

Python将在这个模式上处理你的代码。 所以,如果你写myList[0][0]和你的列表如上定义,Python将像[1]*3那样处理它。 这就是为什么所有列表的第一个元素都被改变了。