在 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 因为这就是它必须与不可变类型(例如str
和int
,更重要的是tuple
和namedtuple
)一起工作的方式。【参考方案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.id
、y.id
和Resource.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__(
在列表上使用 += 时出现 UnboundLocalError。为啥直接调用 __iadd__ 时这里需要 `nonlocal` 可以正常工作?