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

问题描述 投票:0回答:5

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

假设我想创建一个包含 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存储对单个单元格的引用......?

python list nested-lists mutable
5个回答
19
投票

我最好的猜测是,使用

[[]] * 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] 分配了一个新的
value
。但是您没有更改其他元素的value,它们仍然引用它们引用的同一个对象。并且
lst[1]
lst[2]
仍然引用完全相同的实例,因此当然将一个项目附加到
lst[1]
会导致
lst[2]
也看到该更改。

这是人们在使用指针和引用时犯的一个典型错误。这是一个简单的类比。你有一张纸。在上面,你写下某人房子的地址。现在,您拿起那张纸,复印两次,最终得到三张纸,上面写着相同的地址。现在,拿出第一张纸,写下上面写的地址,然后写一个到别人房子的新地址。另外两张纸上写的地址有没有变?不。不过,这正是您的代码所做的。这就是为什么其他两项没有改变。此外,想象一下第二张纸上地址为“still”的房子的主人在他们的房子里建造了一个附加车库。现在我问你,第第三纸上地址的房子有附加车库吗?是的,确实如此,因为它与地址写在“第二张”纸上的房子“一模一样”。这解释了关于第二个代码示例的一切 1:你没想到 Python 会调用“复制构造函数”,是吗?呕吐。 这是因为序列乘法只是重复引用。当您编写

[[]] * 2

时,您创建了一个包含两个元素的新列表,但这两个元素都是内存中的 same 对象,即空列表。因此,一个方面的变化会反映在另一个方面。相比之下,推导式在每次迭代时都会创建一个

新的、独立的

5
投票

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

    
他们引用相同的列表。 还有类似的问题

这里
这里

5
投票

来自

常见问题解答

" * 不创建副本,它只创建对现有的引用 物体。”

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

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


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]

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

© www.soinside.com 2019 - 2024. All rights reserved.