I was told that +=
can have different effects than the standard notation of i = i +
. Is there a case in which i += 1
would be different from i = i + 1
?
3 回答
这完全取决于对象i
。
+=
调用该__iadd__
方法(如果它存在 -__add__
如果它不存在则回退)而+
调用__add__
方法1或__radd__
在少数情况下调用方法2。
从 API 的角度来看,__iadd__
应该用于就地修改可变对象(返回已变异的对象),而__add__
应该返回某物的新实例。对于不可变对象,这两种方法都返回一个新实例,但__iadd__
会将新实例放在当前命名空间中,并与旧实例具有相同的名称。这就是为什么
i = 1
i += 1
似乎增加了i
。实际上,您会得到一个新整数并将其分配在“之上” i
——失去对旧整数的一个引用。在这种情况下,i += 1
与 完全相同i = i + 1
。但是,对于大多数可变对象,情况就不同了:
作为一个具体的例子:
a = [1, 2, 3]
b = a
b += [1, 2, 3]
print a #[1, 2, 3, 1, 2, 3]
print b #[1, 2, 3, 1, 2, 3]
相比:
a = [1, 2, 3]
b = a
b = b + [1, 2, 3]
print a #[1, 2, 3]
print b #[1, 2, 3, 1, 2, 3]
注意在第一个示例中,由于b
和a
引用同一个对象,当我使用+=
on时b
,它实际上发生了变化b
(并且也a
看到了这种变化——毕竟,它引用的是同一个列表)。然而,在第二种情况下,当我这样做时b = b + [1, 2, 3]
,这将获取b
引用的列表并将其与新列表连接起来[1, 2, 3]
。然后它将连接的列表存储在当前命名空间中b
——不考虑b
之前的行。
1在表达式x + y
中,如果x.__add__
没有实现或者如果x.__add__(y)
返回NotImplemented
和 x
并且y
有不同的类型,然后x + y
尝试调用y.__radd__(x)
。所以,如果你有
foo_instance += bar_instance
如果Foo
没有实现__add__
,或者__iadd__
这里的结果是一样的
foo_instance = bar_instance.__radd__(bar_instance, foo_instance)
2在表达式中,如果类型是(例如)类型的子类foo_instance + bar_instance
,bar_instance.__radd__
将在之前尝试。这样做的理由是,从某种意义上说,这是一个“更高级别”的对象,因此应该可以选择覆盖的行为。foo_instance.__add__
bar_instance
foo_instance
issubclass(Bar, Foo)
Bar
Foo
Bar
Foo
在幕后,i += 1
做这样的事情:
try:
i = i.__iadd__(1)
except AttributeError:
i = i.__add__(1)
虽然i = i + 1
做这样的事情:
i = i.__add__(1)
这有点过于简单了,但你明白了:Python+=
通过创建一个__iadd__
方法和一个__add__
.
目的是可变类型,如list
,将在其中改变自己__iadd__
(然后 return self
,除非你正在做一些非常棘手的事情),而不可变类型,如 ,int
不会实现它。
例如:
>>> l1 = []
>>> l2 = l1
>>> l1 += [3]
>>> l2
[3]
因为l2
是同一个对象l1
,而你发生了变异l1
,你也发生了变异l2
。
但:
>>> l1 = []
>>> l2 = l1
>>> l1 = l1 + [3]
>>> l2
[]
在这里,你没有变异l1
;相反,您创建了一个新列表 ,l1 + [3]
并将名称l1
重新指向它,并l2
指向原始列表。
(在该+=
版本中,您还重新绑定l1
了,只是在这种情况下,您将其重新绑定到list
已经绑定的相同位置,因此您通常可以忽略该部分。)
这是一个直接比较的i += x
示例i = i + x
:
def foo(x):
x = x + [42]
def bar(x):
x += [42]
c = [27]
foo(c); # c is not changed
bar(c); # c is changed to [27, 42]