使用乘法(*)意外行为生成子列表

时间:2013-07-17 14:45:03

标签: python list nested-lists mutable

我确信这已在某处得到解答,但我不确定如何描述它。

假设我要创建一个包含3个空列表的列表,如下所示:

lst = [[], [], []]

我认为这样做非常聪明:

lst = [[]] * 3

但是我发现,在调试了一些奇怪的行为之后,这会导致对一个子列表(例如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形式的乘法会导致Python存储对单个单元格的引用......?

5 个答案:

答案 0 :(得分:20)

  

我最好的猜测是,使用[[]] * x形式的乘法会导致Python存储对单个单元格的引用......?

是。你可以自己测试一下

>>> 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]也看到该更改。

这是人们用指针和引用做出的经典错误。这是简单的类比。你有一张纸。在它上面,你写了某人家的地址。你现在拿走那张纸,然后复印两次,这样你就会得到三张纸,上面写着相同的地址。现在,拿出第一张纸,写下写在上面的地址,并写一个新地址给别人的房子。写在另外两张纸上的地址是否有变化?不,那是完全你的代码所做的事情。那是为什么其他两个项目不会改变。此外,想象一下,在第二张纸上仍然地址的房子的主人为他们的房子建造了一个附加车库。现在我问你,那个地址在第三张纸上的房子是否有一个附加车库?是的,它确实如此,因为它第二张纸上的地址写的那个完全相同的房子。这解释了关于第二个代码示例的所有

1 :您没想到Python会调用“复制构造函数”吗?普科。

答案 1 :(得分:5)

这是因为序列乘法仅重复参考。当您编写[[]] * 2时,您创建了一个包含两个元素的新列表,但这两个元素都是内存中的相同对象,即空列表。因此,一方的变化反映在另一方面。相比之下,理解在每次迭代时都会创建一个新的独立列表:

>>> l1 = [[]] * 2
>>> l2 = [[] for _ in xrange(2)]
>>> l1[0] is l1[1]
True
>>> l2[0] is l2[1]
False

答案 2 :(得分:5)

他们引用相同的列表。

类似问题herehere

来自FAQ

  

“*不创建副本,它只创建对现有的引用   对象“。

答案 3 :(得分:1)

您猜测使用[[]] * x形式的乘法会导致Python存储对单个单元格的引用是正确的。

因此,您最终得到了对同一列表的3个引用列表。

答案 4 :(得分:1)

基本上,您的第一个示例中发生的是创建一个列表,其中包含对同一内部列表的多个引用。这是一个细分。

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

以上相当于你的第一个例子。现在每个列表都有自己的变量,希望它更清楚原因。 c[0] is c[1]将评估为True,因为两个表达式都会评估为同一个对象(a)。

您的第二个示例创建了多个不同的内部列表对象。

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