有人告诉我,+=的效果与标准的i = i +符号不同。有没有一种情况下,i += 1会与i = i + 1有所不同?
- 在列表中,+=与extend()的作用类似。
- @Ashwinichaudhary,考虑到i=[1,2,3];i=i+[4,5,6];i==[1,2,3,4,5,6]是True,这是一个相当微妙的区别。许多开发人员可能没有注意到一个操作的id(i)发生了变化,但另一个操作却没有。
- @Kojiro——虽然这是一个微妙的区别,但我认为这是一个重要的区别。
- @这很重要,所以我觉得需要解释。:)
- 有关Java中两个差异的相关问题:StasOfFuff.COM/A/75658/245966
- 在python中,plus等于(+=)的操作可能重复?
这完全取决于对象i。
+=调用__iadd__方法(如果存在,如果不存在,则返回__add__方法),而+在少数情况下调用__add__方法1或__radd__方法2。
从API的角度来看,__iadd__应该被用于在适当的位置修改可变对象(返回发生变化的对象),而__add__应该返回某个对象的新实例。对于不可变对象,这两个方法都返回一个新实例,但__iadd__将把新实例放在与旧实例同名的当前命名空间中。这就是为什么
似乎增加了i。实际上,您会得到一个新的整数,并将其分配到"EDOCX1"(0)的顶部——丢失一个对旧整数的引用。在这种情况下,i += 1与i = i + 1完全相同。但是,对于大多数可变对象,情况就不同了:
作为一个具体的例子:
1 2 3 4 5
| 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] |
号
比较对象:
1 2 3 4 5
| 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)。所以,如果你有
埃多克斯1〔33〕
如果Foo不实现__add__或__iadd__,那么这里的结果与
埃多克斯1〔37〕
2在表达foo_instance + bar_instance时,如果bar_instance的类型是foo_instance类型的一个子类(例如issubclass(Bar, Foo)),bar_instance.__radd__将在foo_instance.__add__之前被尝试。这样做的理由是,从某种意义上说,Bar比Foo是一个"更高层次"的对象,所以Bar应该可以选择推翻Foo的行为。
- 好吧,如果存在,+=会调用__iadd__,然后返回到添加和重新绑定。这就是为什么即使没有int.__iadd__,i = 1; i += 1仍然有效。不过,除了那些小题大做之外,还有很好的解释。
- @Abarnet1——我一直认为int.__iadd__只是叫__add__。我很高兴今天学到了一些新东西:)。
- @abarnet——我想可能是完整的,如果x.__add__不存在(或返回NotImplemented和x和y是不同类型),x + y会调用y.__radd__(x)。
- 如果你真的想要完整,你必须提到"如果它存在"这一点通过了通常的getattr机制,除了经典类的一些奇怪之处,对于在C API中实现的类型,它会寻找nb_inplace_add或sq_inplace_concat,而这些C API函数的要求比python dund更严格。呃,方法,还有……但我认为这与答案无关。主要的区别是,+=在回归到像+那样的行为之前,试图做一个适当的补充,我想你已经解释过了。
- 是的,我想你是对的……尽管我可以退一步说C API不是Python的一部分。这是塞顿的一部分-P
- 进一步使评论讨论复杂化:如果issubclass(type(y), type(x)),x + y首先尝试type(y).__radd__。
- @感谢你的评论,我在底部增加了一段…
- 你的意思是在i上的["]是什么?[ ] ]
- 我的意思是,新对象将被指定为i的名称——无论以前有什么,都离垃圾收集更近了一步。
在封面下,i += 1的做法如下:
1 2 3 4
| try:
i = i.__iadd__(1)
except AttributeError:
i = i.__add__(1) |
。
虽然i = i + 1做了类似的事情:
这有点过于简单化,但您可以理解为:python通过创建__iadd__方法和__add__,为类型提供了一种特别处理+=的方法。
其目的是,像list这样的可变类型将在__iadd__中发生变异(然后返回self,除非您正在做一些非常棘手的事情),而像int这样的不可变类型将不会实现它。
例如:
1 2 3 4 5
| >>> l1 = []
>>> l2 = l1
>>> l1 += [3]
>>> l2
[3] |
。
因为l2与l1是同一对象,而你突变了l1,你也突变了l2。
但是:
1 2 3 4 5
| >>> l1 = []
>>> l2 = l1
>>> l1 = l1 + [3]
>>> l2
[] |
。
在这里,您没有突变l1;相反,您创建了一个新的列表,l1 + [3],并将名称l1反弹到指向它,使l2指向原始列表。
(在+=版本中,您也在重新绑定l1,只是在这种情况下,您将它重新绑定到它已经绑定到的同一个list版本,因此您通常可以忽略该部分。)
- 在发生AttributeError的情况下,__iadd__是否实际呼叫__add__?
- 好吧,i.__iadd__不叫__add__;叫i += 1的是__add__。
- 错误…是的,这就是我的意思。有趣。我没有意识到这是自动完成的。
- 第一次尝试实际上是i = i.__iadd__(1)—iadd可以在适当的位置修改对象,但不必修改,因此在这两种情况下都会返回结果。
- 注意,这意味着operator.iadd调用AttributeError上的__add__,但无法重新绑定结果……所以i=1; operator.iadd(i, 1)返回2,并将i设置为1。这有点令人困惑。
- @你说得对,但那会让人更困惑。您甚至可以使用返回除self以外的内容。让我看看如何在答案中说明这一点。
- @jbernardo:是的,+=总是重新绑定变量,但operator.iadd没有。这就是为什么文件明确地说"a = iadd(a, b)相当于a += b,而不是iadd(a, b)相当于a += b。
- 埃多克斯1〔43〕
下面是一个直接比较i += x和i = i + x的例子:
1 2 3 4 5 6 7 8 9
| 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] |
如果你只是处理文字,那么i += 1和i = i + 1的行为是一样的。
- 你觉得现有的答案没有充分阐明这一事实吗?这似乎是一个不必要的答案给出了广泛的回答米吉尔森。
- 是的,因为现有的答案没有一个简单地回答实际的问题,所以它们涉及到各种方向,而我将这个问题解释为涉及文字的更简单的问题。
- 在Python中,术语"literal"有两种不同的含义,可能两者都不是您所想的。有文字标记(不包括None、-1或'a' 'b')或文字结构(包括列表/元组/dict显示)。在文字结构层面上,i += [1]与i = i + [1]的行为不同,尽管它处理的是列表显示文字。
- 更重要的是,整件事都是一条红鲱鱼。i显然不是字面意思,也不是整个表达式。如果i是定义__iadd__的类的实例,那么i += 1与i = i + 1的行为不同,即使1是字面上的。同样,如果i是int的话,那么i += a与i = i + a的行为相同,即使a不是字面意思。所以,"字面"和任何正确的答案都没有关系。
- @然而,你对这个问题想得太多了,因为操作程序从来没有指定我是什么,所以一个人会得出的逻辑结论是最简单的解释,而不是复杂的解释。
- @乔恩哈伯:鉴于OP接受了米吉尔森的答案,并且(到目前为止)91+26人认为这两个"过度思考"的答案都是好的,你认为你可能读错了OP的意图吗?
- @琼哈伯,他什么都没想过头。即使我们忽略了实现__add__和__iadd__的自定义类(许多python编码人员可能不需要知道),但事实上i += [1]与i = i + [1]的行为不同,这一点对于许多普通的python程序员来说可能是非常重要的,即使他们只是简单地使用python脚本。(这当然与任何阅读这些操作员问题的人有关。)