什么时候"i + = x"与Python中的"i = i + x"不同?

Mar*_*Rob 210 python operators

我被告知+=可以有不同于标准符号的效果i = i +.有没有在这情况下i += 1会从不同i = i + 1

mgi*_*son 314

这完全取决于对象i.

+=调用__iadd__方法(如果它存在 - __add__如果它不存在则重新开启),而在少数情况下+调用__add__方法1__radd__方法2.

从API的角度看,__iadd__应该被用于修改可变对象到位(返回其突变的对象),而__add__应返回一个新实例的东西.对于不可变对象,两个方法都返回一个新实例,但__iadd__会将新实例放在当前命名空间中,其名称与旧实例的名称相同.这就是为什么

i = 1
i += 1
Run Code Online (Sandbox Code Playgroud)

似乎在增加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]
Run Code Online (Sandbox Code Playgroud)

相比:

a = [1, 2, 3]
b = a
b = b + [1, 2, 3]
print a #[1, 2, 3]
print b #[1, 2, 3, 1, 2, 3]
Run Code Online (Sandbox Code Playgroud)

请注意,在第一个例子,因为ba引用同一个对象,当我使用+=b,它实际上改变了b(并a认为这种变化太-毕竟,它引用相同的列表).但是,在第二种情况下,当我这样做时b = b + [1, 2, 3],它会b引用引用的列表并将其与新列表连接起来[1, 2, 3].然后它将连接列表存储在当前命名空间中b- 不考虑b之前的行.


1在该表达式中x + y,如果x.__add__未实现,或者如果x.__add__(y)返回NotImplemented xy具有不同类型的,那么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_instancefoo_instanceissubclass(Bar, Foo)BarFooBarFoo

  • 好吧,`+ =`调用`__iadd__`如果它存在_,则回退到添加和重新绑定.这就是为什么`i = 1; 即使没有`int .__ iadd__`,i + = 1`也能正常工作.但除了那个微小的尼特,很好的解释. (18认同)
  • @abarnert - 我总是假设`int .__ iadd__`刚叫`__add__`.我很高兴今天学到了新东西:). (4认同)

aba*_*ert 67

在幕后,i += 1做这样的事情:

try:
    i = i.__iadd__(1)
except AttributeError:
    i = i.__add__(1)
Run Code Online (Sandbox Code Playgroud)

虽然i = i + 1做了这样的事情:

i = i.__add__(1)
Run Code Online (Sandbox Code Playgroud)

这有点过于简单了,但你明白了:Python给类型提供了一种+=特殊处理方式,通过创建__iadd__方法和方法__add__.

意图是可变类型,比如list,会自我变异__iadd__(然后返回self,除非你做的事情非常棘手),而不可变类型,比如,将int不会实现它.

例如:

>>> l1 = []
>>> l2 = l1
>>> l1 += [3]
>>> l2
[3]
Run Code Online (Sandbox Code Playgroud)

因为l2是同一个对象l1,并且你发生了变异l1,你也会发生变异l2.

但:

>>> l1 = []
>>> l2 = l1
>>> l1 = l1 + [3]
>>> l2
[]
Run Code Online (Sandbox Code Playgroud)

在这里,你没有变异l1; 相反,您创建了一个新列表,l1 + [3]并将名称反弹l1以指向它,并l2指向原始列表.

(在+=版本中,你也是重新绑定l1,只是在那种情况下你将它重新绑定到list已经绑定的那个,所以你通常可以忽略那个部分.)

  • `S/L \([12] \)/ L \\ 1/g` (5认同)
  • 第一次尝试实际上是`i = i .__ iadd __(1)` - `iadd`*可以*修改对象,但不必如此,因此在任何一种情况下都应该返回结果. (3认同)

Deq*_*ing 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]
Run Code Online (Sandbox Code Playgroud)