使用乘法(*)生成子列表出乎意料的行为

我确信这个问题已经在某个地方得到了解答,但我不确定如何描述它。

假设我想创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]

我最好的猜测是使用[[]]*xforms的乘法会导致Python存储对单个单元格的引用…?

我最好的猜测是使用[[]] * xforms的乘法会导致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], [], []]