为什么+=在列表中表现异常?
在Python中,+=
这个操作符在处理列表时似乎表现得有点奇怪。有人能告诉我这是怎么回事吗?
class foo:
bar = []
def __init__(self,x):
self.bar += [x]
class foo2:
bar = []
def __init__(self,x):
self.bar = self.bar + [x]
f = foo(1)
g = foo(2)
print f.bar
print g.bar
f.bar += [3]
print f.bar
print g.bar
f.bar = f.bar + [4]
print f.bar
print g.bar
f = foo2(1)
g = foo2(2)
print f.bar
print g.bar
输出结果
[1, 2]
[1, 2]
[1, 2, 3]
[1, 2, 3]
[1, 2, 3, 4]
[1, 2, 3]
[1]
[2]
foo += bar
似乎会影响到这个类的每一个实例,而foo = foo + bar
的表现则是我所期待的那样。
+=
这个操作符被称为“复合赋值操作符”。
8 个回答
这里的问题是,bar
被定义为类属性,而不是实例变量。
在foo
中,类属性在init
方法里被修改,所以所有的实例都会受到影响。
而在foo2
中,使用(空的)类属性定义了一个实例变量,每个实例都有自己的bar
。
“正确”的实现方式应该是:
class foo:
def __init__(self, x):
self.bar = [x]
当然,类属性是完全合法的。实际上,你可以在不创建类实例的情况下访问和修改它们,方法如下:
class foo:
bar = []
foo.bar = [x]
对于一般情况,可以参考 Scott Griffith的回答。不过在处理像你这样的列表时,+=
操作符其实是 someListObject.extend(iterableObject)
的简写。你可以查看 extend()的文档。
extend
函数会把参数中的所有元素添加到列表中。
当你使用 foo += something
时,你是在原地修改列表 foo
,这意味着你没有改变名字 foo
指向的引用,而是直接改变了列表对象。相反,使用 foo = foo + something
时,你实际上是在创建一个新的列表。
下面的示例代码可以帮助你理解:
>>> l = []
>>> id(l)
13043192
>>> l += [3]
>>> id(l)
13043192
>>> l = l + [3]
>>> id(l)
13059216
注意,当你把新列表重新赋值给 l
时,引用是如何变化的。
由于 bar
是一个类变量,而不是实例变量,原地修改会影响到该类的所有实例。但当你重新定义 self.bar
时,这个实例会有一个独立的实例变量 self.bar
,而不会影响其他类的实例。
简单来说,+=
这个操作符会先尝试调用一个叫__iadd__
的特殊方法,如果这个方法不存在,它就会去用__add__
。所以问题的关键在于这两个特殊方法的区别。
__iadd__
这个方法是用来进行原地加法的,也就是说它会直接修改它作用的对象。而__add__
这个方法则是返回一个新对象,它也被用在普通的+
操作符上。
因此,当你在一个有定义__iadd__
的方法的对象上使用+=
时,这个对象会被直接修改。如果没有__iadd__
,它就会使用普通的__add__
,并返回一个新对象。
这就是为什么对于像列表这样的可变类型,+=
会改变对象的值,而对于像元组、字符串和整数这样的不可变类型,则会返回一个新对象(a += b
相当于a = a + b
)。
对于同时支持__iadd__
和__add__
的类型,你需要小心选择使用哪个。a += b
会调用__iadd__
并修改a
,而a = a + b
则会创建一个新对象并赋值给a
。这两者是不同的操作!
>>> a1 = a2 = [1, 2]
>>> b1 = b2 = [1, 2]
>>> a1 += [3] # Uses __iadd__, modifies a1 in-place
>>> b1 = b1 + [3] # Uses __add__, creates new list, assigns it to b1
>>> a2
[1, 2, 3] # a1 and a2 are still the same list
>>> b2
[1, 2] # whereas only b1 was changed
对于不可变类型(没有__iadd__
的情况),a += b
和a = a + b
是等价的。这就是为什么你可以在不可变类型上使用+=
,这可能看起来是个奇怪的设计决定,但如果没有这个,你就不能在像数字这样的不可变类型上使用+=
了!