python:a+=b与a=a+b不同

Python: a += b not the same as a = a + b

本问题已经有最佳答案,请猛点这里访问。

Possible Duplicate:
What does plus equals (+=) do in Python?

今天我发现了一个关于Python语言的有趣的"特性",这让我非常难过。

1
2
3
4
5
6
7
>>> a = [1, 2, 3]
>>> b ="lol"
>>> a = a + b
TypeError: can only concatenate list (not"str") to list
>>> a += b
>>> a
[1, 2, 3, 'l', 'o', 'l']

这是怎么回事?我以为这两个是对等的!更糟糕的是,这是我花了很多时间调试的代码

1
2
3
4
5
6
7
>>> a = [1, 2, 3]
>>> b = {'omg': 'noob', 'wtf' : 'bbq'}
>>> a = a + b
TypeError: can only concatenate list (not"dict") to list
>>> a += b
>>> a
[1, 2, 3, 'omg', 'wtf']

世界跆拳道联盟!我的代码中有列表和dict,我想知道我到底是怎么在不调用.keys()的情况下将dict的键添加到列表中的。事实证明,就是这样。

我认为这两个说法是对等的。即使忽略了这一点,我也能理解您将字符串附加到列表(因为字符串只是字符数组)中的方式,但是字典呢?如果它附加了一个(键、值)元组的列表,但是只获取要添加到列表中的键似乎是完全任意的。

有人知道这背后的逻辑吗?


这通常是和一直以来都是易变性的问题,特别是运算符重载。C++没有更好的。

表达式a + b从绑定到ab的对象中计算出一个新的列表,这些对象没有被修改。当您将它赋回a时,您将更改一个变量的绑定以指向新值。预计+是对称的,因此不能添加dict和list。

声明a += b修改绑定到a的现有列表。因为它不会更改对象标识,所以对a表示的对象的所有绑定都可以看到这些更改。运算符+=显然不是对称的,它相当于list.extend迭代第二个操作数。对于字典,这意味着要列出键。

讨论:

如果一个对象没有实现+=,那么python将使用+=把它转换成一个等价的语句。因此,根据所涉及对象的类型,这两者有时是等效的。

+=改变引用(而不是操作数值,它是引用)的好处是,在不相应增加实现复杂性的情况下,实现可以更高效。

在其他语言中,您可能会使用更明显的符号。例如,在没有运算符重载的假设版本python中,您可能会看到:

1
a = concat(a, b)

对战

1
a.extend(a, b)

运算符符号实际上只是这些符号的简写。

奖金:

也可以和其他的iTerables一起尝试。

1
2
3
4
5
6
7
8
9
>>> a = [1,2,3]
>>> b ="abc"
>>> a + b
Traceback (most recent call last):
  File"<stdin>", line 1, in <module>
TypeError: can only concatenate list (not"str") to list
>>> a += b
>>> a
[1, 2, 3, 'a', 'b', 'c']

这样做很有用,因为您可以使用+=将生成器附加到列表中,并获取生成器内容。不幸的是,它破坏了与+的兼容性,但哦,好吧。


这背后的原因是因为python列表(在您的例子中是a实现了__iadd__方法,该方法反过来对传递的参数调用__iter__方法。

下面的代码片段更好地说明了这一点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class MyDict(dict):
    def __iter__(self):
        print"__iter__ was called"
        return super(MyDict, self).__iter__()


class MyList(list):
    def __iadd__(self, other):
        print"__iadd__ was called"
        return super(MyList, self).__iadd__(other)


a = MyList(['a', 'b', 'c'])
b = MyDict((('d1', 1), ('d2', 2), ('d3', 3)))

a += b

print a

结果是:

1
2
3
__iadd__ was called
__iter__ was called
['a', 'b', 'c', 'd2', 'd3', 'd1']

python解释器检查一个对象是否实现了__iadd__操作(+=操作),只有当它没有实现时,它才会通过执行一个添加操作,然后执行一个赋值来模拟它。