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

Why is plus-equals valid for list and dictionary?

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

a = []
b = {'hello':'world'}
a += b
>> a now stores ['hello']

The documentation for plus-equals on collections 对我来说并不意味着这应该发生:

For instance, to execute the statement x += y, where x is an instance of a class that has an __iadd__() method, x.__iadd__(y) is called. If x is an instance of a class that does not define a __iadd__() method, x.__add__(y) and y.__radd__(x) are considered, as with the evaluation of x + y

但是,从逻辑上讲,两者都是

a + b # TypeError Exception

b + a # TypeError Exception

未定义。此外,b+=a 也会引发 TypeError。我没有看到任何可以解释事情的特殊实现 in the source,但我不是 100% 确定去哪里看。

我发现的关于 SO 的最接近的问题是 this one, asking about += on dictionaries, but that's just asking about a data structure with itself. This one 有一个关于列表 self-addition 的有前途的标题,但它声称“__add__”正在幕后应用,这不应该不能在列表和字典之间定义。

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

My best guess is that the iadd is invoking extend, which is defined here, and then it tries to iterate over the dictionary, which in turn yields its keys. But this seems... weird? And I don't see any intuition of that coming from the docs.

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

In the docs you can see that in fact __iadd__ is equivalent to .extend(), and here 它说:

list.extend(iterable): Extend the list by appending all the items from the iterable.

In the part about dicts 它说:

Performing list(d) on a dictionary returns a list of all the keys used in the dictionary

总而言之,a_list += a_dict 等价于 a_list.extend(iter(a_dict)),后者等价于 a_list.extend(a_dict.keys()),它将使用字典中的键列表扩展列表。

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


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

dict iterator returning keys:

static PyObject *
dict_iter(PyDictObject *dict)
{
    return dictiter_new(dict, &PyDictIterKey_Type);
}

list.extend(iterable) calling iter() on its argument:

static PyObject *
list_extend(PyListObject *self, PyObject *iterable)
{
    ...
    it = PyObject_GetIter(iterable);
    ...
}

+= being equivalent to list.extend():

static PyObject *
list_inplace_concat(PyListObject *self, PyObject *other)
{
    ...
    result = list_extend(self, other);
    ...
}

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