在 Python 中覆盖“+=”? (__iadd__() 方法)

Posted

技术标签:

【中文标题】在 Python 中覆盖“+=”? (__iadd__() 方法)【英文标题】:Overriding "+=" in Python? (__iadd__() method) 【发布时间】:2010-11-06 00:43:35 【问题描述】:

是否可以在 Python 中覆盖 +=?

【问题讨论】:

【参考方案1】:

是的,覆盖__iadd__ 方法。示例:

def __iadd__(self, other):
    self.number += other.number
    return self    

【讨论】:

如果你的类代表不可变对象,你不应该实现__iadd__。在这种情况下,只需实现__add__,它将用于覆盖+=。例如,您可以在字符串和整数等不可变类型上使用 +=,而使用 __iadd__ 则无法做到这一点。 @ScottGriffiths,你是说如果你已经实现了__add__,你就不一定要实现__iadd__?我阅读了您链接的重复问题,但我只是感到困惑,因为我不明白您为什么要实现 __add__ 以便它改变对象 @ScottGriffiths 的意思是问“你不一定要实现 __iadd__ 才能使用 +=?” @JosieThompson:如果您不实现__iadd__,那么它将使用__add__(如果可用),这通常很好。所以在这种情况下,a += b 将等同于a = a + b,它为a 分配一个新值,而不是更改a 本身。拥有一个单独的 __iadd__ 通常是一个很好的优化,而不是您需要使用 += 运算符。 @ScottGriffiths __imul__ 也一样吗?【参考方案2】:

除了上述答案中正确给出的内容外,值得明确说明的是,当 __iadd__ 被覆盖时,x += y 操作不会以 __iadd__ 方法的结尾结束。

相反,它以x = x.__iadd__(y) 结尾。换句话说,在实现完成之后,Python 会将您的 __iadd__ 实现的返回值分配给您“添加到”的对象。

这意味着可以改变x += y 操作的左侧,以使最终的隐式步骤失败。考虑一下当您添加到列表中的内容时会发生什么:

>>> x[1] += y # x has two items

现在,如果您的 __iadd__ 实现(x[1] 上的对象的方法)错误地或故意从列表的开头删除了第一项 (x[0]),那么 Python 将运行您的 __iadd__方法)并尝试将其返回值分配给x[1]。它将不再存在(它将位于x[0]),从而产生ÌndexError

或者,如果您的__iadd__ 在上述示例的x 的开头插入了一些内容,那么您的对象将位于x[2],而不是x[1],并且之前在x[0] 的任何内容现在将位于@ 987654340@ 并被分配__iadd__ 调用的返回值。

除非人们了解正在发生的事情,否则由此产生的错误可能是一场难以修复的噩梦。

【讨论】:

你知道为什么__iadd__是这样设计的吗?即为什么它分配返回值而不是仅仅解决就地突变? @joel 因为这就是它必须与不可变类型(例如strint,更重要的是tuplenamedtuple)一起工作的方式。【参考方案3】:

除了重载__iadd__(记得返回self!),您还可以回退__add__,因为x += y 将像x = x + y 一样工作。 (这是 += 运算符的缺陷之一。)

>>> class A(object):
...   def __init__(self, x):
...     self.x = x
...   def __add__(self, other):
...     return A(self.x + other.x)
>>> a = A(42)
>>> b = A(3)
>>> print a.x, b.x
42 3
>>> old_id = id(a)
>>> a += b
>>> print a.x
45
>>> print old_id == id(a)
False

它甚至trips up experts:

class Resource(object):
  class_counter = 0
  def __init__(self):
    self.id = self.class_counter
    self.class_counter += 1

x = Resource()
y = Resource()

您希望x.idy.idResource.class_counter 具有什么值?

【讨论】:

您的第二个示例与 iadd 或 += 无关。如果你使用 self.class_counter = self.class_counter + 1 也会出现同样的结果。这只是一个范围问题,应该使用 Resource 时使用 self。 这是一个使用 += 会导致问题的例子。如果您正在重载 iadd,那么您就是在向您的班级的用户(包括您自己)开放,至少,您应该事先知道问题的存在。 @FogleBird:这是一个陷阱,因为foo += bar 可以表示“改变foo 引用的现有对象”或“将foo 分配给由表达式foo + bar 产生的对象”。这取决于foo 是否有__iadd__ 方法。【参考方案4】:

http://docs.python.org/reference/datamodel.html#emulating-numeric-types

例如,执行语句 x += y,其中 x 是 a 的一个实例 具有 __iadd__() 方法的类, x.__iadd__(y) 被调用。

【讨论】:

以上是关于在 Python 中覆盖“+=”? (__iadd__() 方法)的主要内容,如果未能解决你的问题,请参考以下文章

流畅python学习笔记:第十三章:重载运算符__add__,__iadd__,__radd__,__mul__,__rmul__,__neg__,__eq__,__invert__,__pos__(

python中a+=a和a=a+a的区别

在列表上使用 += 时出现 UnboundLocalError。为啥直接调用 __iadd__ 时这里需要 `nonlocal` 可以正常工作?

序列的增量赋值 +=和*=

替代=登录python [重复]

+= 在 Python 中究竟做了啥?