我今天发现了一个有趣的蟒蛇语“特征”,这给了我很多悲伤。
>>> 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']
那是怎么回事?我以为这两个本来是相同的!更糟糕的是,这是我有一段时间调试的代码
>>> 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']
WTF!我在我的代码中有列表和dicts,并且想知道我到底是怎么把我的dict的键附加到列表而没有调用.keys()。事实证明,这是如何。
我认为这两个陈述是等同的。即使忽略这一点,我也可以理解你将字符串附加到列表上的方式(因为字符串只是字符数组)但是字典?也许如果它附加了一个(键,值)元组列表,但只抓取添加到列表中的键似乎是完全随意的。
有谁知道这背后的逻辑?
答案 0 :(得分:35)
这一直是一直存在的问题,特别是运算符重载。 C ++并不是更好。
表达式a + b
计算绑定到a
和b
的对象的新列表,这些列表未被修改。将此值分配回a
时,可以更改一个变量的绑定以指向新值。预期+
是对称的,因此您无法添加词典和列表。
语句a += b
修改绑定到a
的现有列表。由于它不会更改对象标识,因此对a
所表示的对象的所有绑定都可以看到更改。运算符+=
显然不是对称的,它等于list.extend
,它迭代第二个操作数。对于词典,这意味着列出键。
<强>讨论:强>
如果某个对象未实现+=
,则Python会使用+
和=
将其转换为等效语句。所以这两个有时是等价的,取决于所涉及对象的类型。
改变参照的+=
的好处(与作为参考的操作数值相反)是实现可以更高效,而不会相应地增加实现复杂性。
在其他语言中,您可能会使用更明显的表示法。例如,在没有运算符重载的Python的假设版本中,您可能会看到:
a = concat(a, b)
与
a.extend(a, b)
操作符号实际上只是这些的简写。
<强>加成:强>
也可以尝试其他迭代。
>>> 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']
能够执行此操作非常有用,因为您可以将生成器附加到具有+=
的列表并获取生成器内容。不幸的是,它破坏了与+
的兼容性,但是很好。
答案 1 :(得分:5)
这背后的原因是因为python列表(在您的情况下为a
)实现了__iadd__
方法,该方法又在传递的参数上调用__iter__
方法。
以下代码段更好地说明了这一点:
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
结果是:
__iadd__ was called
__iter__ was called
['a', 'b', 'c', 'd2', 'd3', 'd1']
python解释器检查一个对象是否实现了__iadd__
操作(+=
),并且只有当它没有执行时,才会通过执行添加操作然后进行赋值来模拟它。