11

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

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

4

5 回答 5

19

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

是的。你可以自己测试一下

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

这表明所有三个引用都指向同一个对象。请注意,发生这种情况确实很有意义1。它只是复制values,在这种情况下,这些值引用。这就是为什么您会看到相同的参考文献重复了三遍。

有趣的是,如果我这样做

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 会调用“复制构造函数”吗?呕吐。

于 2013-07-17T14:47:33.957 回答
5

他们引用了相同的列表。

这里这里有类似的问题

常见问题解答

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

于 2013-07-17T14:49:51.127 回答
5

这是因为序列乘法只是重复引用。当你写的时候[[]] * 2,你创建了一个包含两个元素的新列表,但是这两个元素都是内存中的同一个对象,即一个空列表。因此,一个变化反映在另一个上。相比之下,推导式在每次迭代中创建一个新的独立列表:

>>> l1 = [[]] * 2
>>> l2 = [[] for _ in xrange(2)]
>>> l1[0] is l1[1]
True
>>> l2[0] is l2[1]
False
于 2013-07-17T14:48:01.880 回答
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], [], []]
于 2013-07-17T14:54:22.097 回答
1

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

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

于 2013-07-17T14:50:04.713 回答