【问题标题】:Generating sublists using multiplication ( * ) unexpected behavior [duplicate]使用乘法 ( * ) 意外行为生成子列表 [重复]
【发布时间】:2013-07-16 04:36:27
【问题描述】:

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

假设我想创建一个包含 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


【解决方案1】:

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

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

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

这表明所有三个引用都指向同一个对象。请注意,真的发生这种情况是完全合理的1。它只是复制,在这种情况下,值引用。这就是为什么您会看到相同的参考文献重复了 3 次。

有趣的是,如果我这样做了

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,它们仍然引用它们所引用的同一个对象。而lst[1]lst[2] 仍然指代完全相同的实例,所以当然将一个项目附加到lst[1] 会导致lst[2] 也看到该更改。

这是人们在使用指针和引用时常犯的典型错误。这是一个简单的类比。你有一张纸。在上面写上某人家的地址。你现在拿那张纸,复印两次,最后得到三张纸,上面写着相同的地址。现在,拿起第一张纸,潦草地写下写在上面的地址,然后写下别人家的新地址。另外两张纸上写的地址有变化吗?不,这正是您的代码所做的。这就是为什么其他两项没有改变。此外,假设房子的主人在第二张纸上的地址仍然为他们的房子建造了一个附加车库。现在我问你,地址在第三张张纸上的房子有附加车库吗?是的,确实如此,因为它与第二张纸上写有地址的那所房子完全相同。这解释了关于您的第二个代码示例的一切

1:您没想到 Python 会调用“复制构造函数”吧?呕吐。

【讨论】:

  • +1 表示id()。这会很有用。
  • 感谢id(x),garage build-on 是指针编辑的一个很好的例子。
【解决方案2】:

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

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

【讨论】:

    【解决方案3】:

    它们引用了相同的列表。

    herehere也有类似的问题

    来自FAQ

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

    【讨论】:

    • +1 表示 Python 文档的链接。
    【解决方案4】:

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

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

    【讨论】:

      【解决方案5】:

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

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

      【讨论】:

      • 我没想过要尝试“is”运算符,谢谢!
      • @AdrianWan 没问题,很高兴我能帮上忙。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-06-12
      • 2021-08-28
      • 2016-08-13
      • 1970-01-01
      • 2016-02-14
      • 2013-05-30
      相关资源
      最近更新 更多