为什么 plus-equals 对列表和字典有效?

en_*_*ght 13 python dictionary list operator-overloading

使用__iadd__符号将字典添加到列表似乎将字典的键添加为列表中的元素。为什么?例如

a = []
b = {'hello':'world'}
a += b
>> a now stores ['hello']
Run Code Online (Sandbox Code Playgroud)

集合上的plus-equals 文档对我来说并不意味着这应该发生:

例如,要执行语句 x += y,其中 x 是具有__iadd__() 方法的类的实例,x.__iadd__(y)被调用。如果 x 是一个没有定义__iadd__()方法的类的实例,x.__add__(y)并且y.__radd__(x)被考虑,就像评估x + y

但是,从逻辑上讲,两者

a + b # 类型错误异常

b + a # 类型错误异常

没有定义。此外,b+=a也会引发 TypeError。我在源代码中没有看到任何可以解释事物的特殊实现,但我不能 100% 确定在哪里查看。

我发现的关于 SO 的最接近的问题是this one,询问字典上的 += ,但这只是询问自身的数据结构。这个有一个关于列表自添加的有希望的标题,但它声称“ __add__”是在幕后应用的,不应该在列表和字典之间定义。

我最好的猜测是__iadd__调用了在此处定义的扩展,然后它尝试迭代字典,从而产生它的键。但这似乎……很奇怪?而且我没有看到来自文档的任何直觉。

Tha*_*rdo 14

我最好的猜测是iadd正在调用这里定义的扩展,然后它尝试遍历字典,从而产生它的键。但这似乎……很奇怪?而且我没有看到来自文档的任何直觉。

这是为什么会发生这种情况的正确答案。我找到了相关的文档说这个 -

在文档中,您可以看到实际上__iadd__等效于.extend()这里说:

list.extend(iterable):通过添加可迭代对象中的所有项目来扩展列表。

在关于 dicts 的部分它说:

对字典执行 list(d) 返回字典中使用的所有键的列表

总而言之,a_list += a_dict相当于a_list.extend(iter(a_dict)),相当于a_list.extend(a_dict.keys()),这将使用字典中的键列表扩展列表。

我们也许可以讨论为什么会是这样,但我认为我们不会找到明确的答案。我认为这+=是一个非常有用的简写.extend,而且字典应该是可迭代的(我个人更喜欢它返回.items(),但是哦)


编辑:您似乎对 CPython 的实际实现感兴趣,所以这里有一些代码指针:

dict 迭代器返回键

static PyObject *
dict_iter(PyDictObject *dict)
{
    return dictiter_new(dict, &PyDictIterKey_Type);
}
Run Code Online (Sandbox Code Playgroud)

list.extend(iterable) 在其参数上调用 iter()

static PyObject *
list_extend(PyListObject *self, PyObject *iterable)
{
    ...
    it = PyObject_GetIter(iterable);
    ...
}
Run Code Online (Sandbox Code Playgroud)

+= 等价于 list.extend()

static PyObject *
list_inplace_concat(PyListObject *self, PyObject *other)
{
    ...
    result = list_extend(self, other);
    ...
}
Run Code Online (Sandbox Code Playgroud)

然后这个方法似乎在上面引用了一个PySequenceMethods结构体,它似乎是序列的抽象,定义了常见的操作,例如就地连接和正常连接(定义为list_concat在同一个文件中,你可以看到不是相同)。