我被告知+=
可能会产生与i = i +
标准符号不同的效果。是否存在i += 1
与i = i + 1
不同的情况?
答案 0 :(得分:304)
这完全取决于对象i
。
+=
调用__iadd__
method(如果它存在 - 如果它不存在则返回__add__
),而+
调用__add__
method < sup> 1 或__radd__
method in a few cases 2 。
从API的角度来看,__iadd__
应该用于修改到位的可变对象(返回被突变的对象),而__add__
应该返回一个新实例的东西。对于 immutable 对象,两个方法都返回一个新实例,但__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
引用同一个对象,当我在+=
上使用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__
之前尝试{/ em>类型{{ 1}}是bar_instance
类型的子类(例如foo_instance
)。这样做的理由是因为issubclass(Bar, Foo)
在某种意义上是一个“更高级别”的对象而不是Bar
所以Foo
应该可以选择覆盖Bar
的行为。< / SUP>
答案 1 :(得分:66)
在幕后,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__
中变异(然后返回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
,因此您可以通常会忽略那部分。)
答案 2 :(得分:5)
以下示例直接将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]
答案 3 :(得分:-3)
如果您只是处理文字,那么i += 1
与i = i + 1
具有相同的行为。