【问题标题】:What was the motivation for doing lists augmented assignment (+=) in place in python? [duplicate]在 python 中进行列表增强赋值 (+=) 的动机是什么? [复制]
【发布时间】:2011-07-11 16:56:06
【问题描述】:
>>> list1 = []
>>> list2 = list1
>>> list2 += [1]
>>> print list1
[1]

对比一下

>>> list1 = []
>>> list2 = list1
>>> list2 = list2 + [1]
>>> print list1
[]

'+='-操作修改原始列表是否有原因?

编辑:只是为了让我的问题更清楚

在我知道的大多数语言中,'+='-操作符不能这样工作,我想知道为什么它在 python 中是这样设计的。

一些例子:

红宝石

irb(main):001:0> l = []
irb(main):002:0> a = l
irb(main):003:0> a += [2]
irb(main):004:0> l
=> []

Scala 等。

【问题讨论】:

  • 有趣。可能是因为在list2 = list2 + [1] 中,list2 + [1] 创建了一个新列表,然后将其分配给list2。我想这是 pythonista 习惯的那些“怪癖”之一。不过,这对初学者来说是令人困惑的。
  • @MartijnPieters:我认为问题有点不同。我在这里问设计的原因(因为恕我直言,与同一运算符的工作方式相比有点奇怪,例如Scala 和 Ruby),另一个问题更多是关于“发生了什么?”。
  • 查看original PEP (Python Enhancement Proposal 了解动机。 将这个特性添加到 Python 有两个主要原因:表达的简单性和对就地操作的支持。最终结果是在语法简单性和表达简单性之间进行权衡;像大多数新功能一样,增强分配不会添加以前不可能的任何内容。它只是让这些事情更容易做。
  • @MartijnPieters:PEP 讲述了昂贵的矩阵运算。无论如何,我不太确定关于编程的问题,因为我真的只是想知道为什么有人认为这样做是个好主意为列表就地执行此操作。
  • 在列表中就地执行此操作是一个绝妙的主意

标签: python


【解决方案1】:

reference 存储的 Python 中的列表。

这意味着当您执行list2 = list1 时,您并没有制作列表的副本 - 您只是在说“list2list1 所做的相同”,即您最初创建的列表你做了list1 = []

Python 将+= 指定为列表的“就地附加”,因为大多数时候当您在列表上使用+= 时,这就是您想要做的——您通常不想创建新的每次添加元素时都会列出。

因此,当您附加到 list2(它“指的是 list1 所指的同一个对象”)然后从 list1 中读取时,您会看到附加的项目,正如预期的那样,因为它们都指向相同的列表。

但是,对于+,总是会创建一个新列表,因为修改其中任何一个操作数都没有意义(因为a+b 并不意味着修改ab )。

因此,当您执行list2 = list2 + [1] 时,您将创建一个新列表,其中包含list21 指向的原始对象的所有内容,然后说list2 现在引用了该新列表列表。由于它现在引用的列表与 list1 不同,因此当您从 list1 读取时,您仍然会看到没有额外 1 的原始列表。

【讨论】:

  • +1:我认为这是 OP 混乱的真正根源。
【解决方案2】:

来自 Python 2.6.4 文档,第 6.2.1 节。 (增强的赋值语句)

x += 1 这样的增强赋值表达式可以重写为x = x + 1 以实现类似但不完全相同的效果。在增强版本中,x 只被评估一次。此外,如果可能,实际操作在原地执行,这意味着不是创建新对象并将其分配给目标,而是修改旧对象。

[强调添加]

【讨论】:

  • 只是对后台发生的事情的一点额外解释:实际上,这两个赋值不使用相同的对象方法,这就是行为不同的原因。 x = x + 1 使用__add__() 方法,返回一个新对象,而x += 1 使用__iadd__() 方法,就地修改对象。
【解决方案3】:

参见documentation regarding Emulating numeric types,它描述了实现此行为的方法。这也适用于列表:

调用这些方法来实现增强的算术分配 (+=, -=, *=, /=, //=, %=, **=, <<=, >>=, &=, ^=, |=)。 这些方法应该尝试就地执行操作(修改self)并返回结果(可以是但不一定是self)。如果未定义特定方法,则扩充分配回退到正常方法。例如,要执行语句x += y,其中x 是具有__iadd__() 方法的类的实例,调用x.__iadd__(y)。如果x 是未定义__iadd__() 方法的类的实例,则考虑x.__add__(y)y.__radd__(x),与评估x + y 一样。

【讨论】:

    【解决方案4】:

    当您执行list2 += [1] 时,您正在修改列表。这就是为什么您不更改列表指向的引用,而是直接更改列表的原因。当您执行list2 = list2 + [1] 时,您正在创建一个新列表。

    >>> l = []
    >>> id(l)
    41523720L
    >>> l += [3]
    >>> id(l)
    41523720L     # same as l = []
    >>> l = l+[3]
    >>> id(l)
    41532232L
    

    这就解释了差异。

    【讨论】:

      【解决方案5】:

      嗯,因为它是这样工作的。当您编写list2 = list2 + [1] 时,您会创建新列表并将其绑定到list2 名称。当您使用+= 时,操作会“就地”进行。所以当list1list2 引用同一个对象时,这里就是这种情况,你用+= 操作符修改它。

      【讨论】:

        【解决方案6】:

        += 应该是这样工作的。一般来说,

        a += b
        

        意思

        a = a + b
        

        但是您的特定场景有不同的问题。当你写

        list2 = list1
        

        没有复制; list2 现在是对同一个列表的引用。对list2 的任何修改都将在list1 中可见,反之亦然。

        在您的第二个代码 sn-p 中,list2 + [1] 构造了一个 new 列表,该列表随后被分配给 list2。由于此副本独立于 list1,因此对 list1 的更改不可见。

        (Nitpicker 的角落:使用运算符重载,可以构造一个对 += 行为不同的类。不要。只是......不要。)

        【讨论】:

        • 那为什么不这样呢?
        • 但是 OP 说您在回答中提到的行为没有发生!
        • 对不起,我的回答太仓促了。已编辑。
        【解决方案7】:

        在python中,你认为的变量名在大多数情况下更类似于指针; "=" 不会复制对象,它会将新名称绑定到该对象(在其他上下文中为“通过引用复制”)。所以list2 = list1 意味着这两个名称都指向 same 列表,而不仅仅是相同列表的两个副本。因此,"+=" 修改了两个名称所指向的单个列表。

        您可以逐个元素复制列表 (list2 = [i for in list1]) 或使用 copy 模块 (list2 = copy.copy(list1))

        【讨论】:

          猜你喜欢
          • 2021-04-19
          • 2020-05-16
          • 2011-01-17
          • 2013-07-19
          • 2019-11-23
          • 2017-06-20
          • 2013-05-10
          • 1970-01-01
          • 2011-01-01
          相关资源
          最近更新 更多