使用乘法(*)生成子列表出乎意料的行为
我确信这个问题已经在某个地方得到了解答,但我不确定如何描述它。
假设我想创build一个包含3个空列表的列表,如下所示:
lst = [[], [], []]
我以为我这样做很聪明:
lst = [[]] * 3
但是我发现,在debugging了一些奇怪的行为之后,这引起了一个附加的更新,比如说lst[0].append(3)
,来更新整个列表,使[[3], [3], [3]]
而不是[[3], [], []]
。
但是,如果我初始化列表
lst = [[] for i in range(3)]
然后做lst[1].append(5)
给出预期的[[], [5], []]
我的问题是为什么会发生这种情况 ? 有意思的是,如果我这样做
lst = [[]]*3 lst[0] = [5] lst[0].append(3)
那么单元0的'连接'就会被破坏,我得到[[5,3],[],[]]
,但是lst[1].append(0)
仍然会导致[[5,3],[0],[0]
。
我最好的猜测是使用[[]]*x
forms的乘法会导致Python存储对单个单元格的引用…?
我最好的猜测是使用
[[]] * x
forms的乘法会导致Python存储对单个单元格的引用…?
是。 你可以自己testing一下
>>> lst = [[]] * 3 >>> print [id(x) for x in lst] [11124864, 11124864, 11124864]
这表明所有三个引用都指向同一个对象。 并注意,这真的很有道理,这发生1 。 它只是复制值 ,在这种情况下,值是引用。 这就是为什么你看到相同的参考重复三次。
有意思的是,如果我这样做
lst = [[]]*3 lst[0] = [5] lst[0].append(3)
那么单元0的'连接'就会被破坏,我得到
[[5,3],[],[]]
,但是lst[1].append(0)
仍然会导致[[5,3],[0],[0]
。
你改变了占用lst[0]
的引用; 也就是说,您为lst[0]
分配了一个新的值 。 但是你没有改变其他元素的价值 ,他们仍然指的是他们提到的同一个对象。 而lst[1]
和lst[2]
仍然指的是完全相同的实例,所以当然附加一个项目到lst[1]
导致lst[2]
也看到这个改变。
这是人们用指针和引用做出的经典错误。 这是简单的比喻。 你有一张纸。 在上面写上别人家的地址。 你现在拿起那张纸,复印两遍,最后得到三张纸,上面写着相同的地址。 现在拿第一张纸,上面写上地址,写一个新的地址给别人的房子。 另外两张纸上写的地址是否改变了? 不,那正是你的代码所做的。 这就是为什么其他两个项目不会改变。 此外,想象一下, 仍然在第二张纸上的房屋的所有者在他们的房屋中build立了一个附加的车库。 现在我问你,那个地址在第三张纸上的房子是否有附加车库? 是的,因为它和第二张纸上的地址完全一样。 这解释了你的第二个代码示例的一切 。
1 :你没有想到Python会调用“拷贝构造函数”吗? 普科。
这是因为序列乘法只是重复参考。 当你写[[]] * 2
,你用两个元素创build一个新的列表,但是这两个元素都是内存中的同一个对象,即一个空列表。 因此,一个中的变化反映在另一个中。 理解,相反,创build一个新的,每个迭代的独立清单:
>>> l1 = [[]] * 2 >>> l2 = [[] for _ in xrange(2)] >>> l1[0] is l1[1] True >>> l2[0] is l2[1] False
他们正在引用相同的列表。
这里和这里也有类似的问题
从FAQ :
“*不创build副本,它只创build对现有对象的引用”。
你猜测使用[[]] * xforms的乘法会导致Python存储对单个单元的引用是正确的。
所以你最后列出了3个对同一个列表的引用。
基本上你的第一个例子中发生的事情是,一个列表被创build时,对同一个内部列表有多个引用。 这是一个故障。
>>> a = [] >>> b = [a] >>> c = b * 3 # c now contains three references to a >>> d = [ a for _ in xrange(4) ] # and d contains four references to a >>> print c [[], [], []] >>> print d [[], [], [], []] >>> a.append(3) >>> print c [[3], [3], [3]] >>> print d [[3], [3], [3], [3]] >>> x = [[]] * 3 # shorthand equivalent to c >>> print x [[], [], []] >>> x[0].append(3) >>> print x [[3], [3], [3]]
以上相当于你的第一个例子。 现在每个列表都有自己的variables,希望更清楚为什么。 c[0] is c[1]
将评估为True
,因为两个expression式评估的是相同的对象( a
)。
第二个例子创build了多个不同的内部列表对象。
>>> c = [[], [], []] # this line creates four different lists >>> d = [ [] for _ in xrange(3) ] # so does this line >>> c[0].append(4) >>> d[0].append(5) >>> print c [[4], [], []] >>> print d [[5], [], []]