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

Generating sublists using multiplication ( * ) unexpected behavior

本问题已经有最佳答案,请猛点这里访问。

我肯定有人回答了这个问题,但我不知道该如何描述。

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

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

我觉得我做这件事很聪明:

1
lst = [[]] * 3

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

但是,如果我用

1
lst = [[] for i in range(3)]

那么做lst[1].append(5)就得到了预期的[[], [5], []]

我的问题是为什么会这样?有趣的是,如果我这样做的话

1
2
3
lst = [[]]*3
lst[0] = [5]
lst[0].append(3)

然后,细胞0的"连接"断了,我得到了[[5,3],[],[]],但lst[1].append(0)仍然引起[[5,3],[0],[0]

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


My best guess is that using multiplication in the form [[]] * x causes Python to store a reference to a single cell...?

对。你可以自己测试这个

1
2
3
>>> lst = [[]] * 3
>>> print [id(x) for x in lst]
[11124864, 11124864, 11124864]

这表明所有三个引用都指向同一个对象。请注意,这件事真的很有道理。它只复制值,在本例中,值是引用。这就是为什么你看到同一个参考重复三次的原因。

It is interesting to note that if I do

1
2
3
lst = [[]]*3
lst[0] = [5]
lst[0].append(3)

then the 'linkage' of cell 0 is broken and I get [[5,3],[],[]], but lst[1].append(0) still causes [[5,3],[0],[0].

您更改了占用lst[0]的引用;也就是说,您为lst[0]分配了一个新值。但您没有更改其他元素的值,它们仍然引用它们所引用的同一对象。而lst[1]lst[2]仍然指的是完全相同的情况,因此,在lst[1]上附加一个项目当然会使lst[2]也看到这种变化。

这是人们在使用指针和引用时犯的一个典型错误。这是一个简单的类比。你有一张纸。在上面写下某人家的地址。你现在拿着那张纸,复印两次,这样你就得到了三张写着同样地址的纸。现在,拿第一张纸,草草写下写在上面的地址,然后给别人的房子写一个新地址。另外两张纸上的地址有变化吗?不,不过这正是你的代码所做的。这就是其他两项不变的原因。再者,想象一下,地址还在第二张纸上的房子的主人为他们的房子建造了一个附加车库。现在我问你,地址在第三张纸上的房子有附加车库吗?是的,是的,因为它和写在第二张纸上的地址完全一样。这解释了关于第二个代码示例的所有内容。

1:你没想到python会调用一个"复制构造函数",对吧?呕吐。


它们引用的是相同的列表。

这里也有类似的问题

从常见问题解答:

" * doesn’t create copies, it only creates references to the existing
objects."


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

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


基本上,在第一个示例中发生的事情是,正在创建一个列表,其中包含对同一内部列表的多个引用。这是故障报告。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
>>> 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)。

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

1
2
3
4
5
6
7
8
>>> 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], [], []]

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

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