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

A.W*_*Wan 11 python list mutable nested-lists

我确信这已在某个地方得到解答,但我不确定如何描述它.

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

lst = [[], [], []]
Run Code Online (Sandbox Code Playgroud)

这样做我以为我很聪明:

lst = [[]] * 3
Run Code Online (Sandbox Code Playgroud)

但是我发现,在调试了一些奇怪的行为之后,这导致了对一个子列表的追加更新,比如lst[0].append(3),更新整个列表,[[3], [3], [3]]而不是[[3], [], []].

但是,如果我用列表初始化列表

lst = [[] for i in range(3)]
Run Code Online (Sandbox Code Playgroud)

然后做lst[1].append(5)了预期的[[], [5], []]

我的问题是为什么会发生这种情况?有趣的是,如果我这样做

lst = [[]]*3
lst[0] = [5]
lst[0].append(3)
Run Code Online (Sandbox Code Playgroud)

那么单元格0的"连接"就被打破了[[5,3],[],[]],但是我得到了,但lst[1].append(0)仍然会导致[[5,3],[0],[0].

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

jas*_*son 20

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

是.你可以自己测试一下

>>> lst = [[]] * 3
>>> print [id(x) for x in lst]
[11124864, 11124864, 11124864]
Run Code Online (Sandbox Code Playgroud)

这表明所有三个引用都引用同一个对象.请注意,发生这种情况真的很有意义1.它只是复制,在这种情况下,值引用.这就是为什么你看到相同的参考重复三次.

有趣的是,如果我这样做

lst = [[]]*3
lst[0] = [5]
lst[0].append(3)
Run Code Online (Sandbox Code Playgroud)

那么单元格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会调用"复制构造函数"吗?普科.


ars*_*jii 5

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

>>> l1 = [[]] * 2
>>> l2 = [[] for _ in xrange(2)]
>>> l1[0] is l1[1]
True
>>> l2[0] is l2[1]
False
Run Code Online (Sandbox Code Playgroud)


doc*_*ove 5

他们引用相同的列表.

这里这里也有类似的问题

FAQ:

"*不会创建副本,它只会创建对现有对象的引用."