为什么+=在列表中表现异常?

175 投票
8 回答
122851 浏览
提问于 2025-04-15 19:48

在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 个回答

22

这里的问题是,bar被定义为类属性,而不是实例变量。

foo中,类属性在init方法里被修改,所以所有的实例都会受到影响。

而在foo2中,使用(空的)类属性定义了一个实例变量,每个实例都有自己的bar

“正确”的实现方式应该是:

class foo:
    def __init__(self, x):
        self.bar = [x]

当然,类属性是完全合法的。实际上,你可以在不创建类实例的情况下访问和修改它们,方法如下:

class foo:
    bar = []

foo.bar = [x]
110

对于一般情况,可以参考 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,而不会影响其他类的实例。

199

简单来说,+=这个操作符会先尝试调用一个叫__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 += ba = a + b是等价的。这就是为什么你可以在不可变类型上使用+=,这可能看起来是个奇怪的设计决定,但如果没有这个,你就不能在像数字这样的不可变类型上使用+=了!

撰写回答