Adding a dictionary to a list using the __iadd__
notation seems to add the keys of the dictionary as elements in the list. Why? For example
a = []
b = {'hello': 'world'}
a += b
print(a) # -> ['hello']
The documentation for plus-equals on collections doesn't imply to me that this should happen:
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)
andy.__radd__(x)
are considered, as with the evaluation ofx + y
But, logically, both a + b
and b + a
raise a TypeError. Furthermore, b += a
raises a TypeError too. I don't see any special implementation in the source that would explain things, but I'm not 100% sure where to look.
The closest question on SO I found is this one, asking about += on dictionaries, but that's just asking about a data structure with itself. This one had a promising title about list self-addition, but it claims "__add__
" is being applied under the hood, which shouldn't be defined between lists and dictionaries.
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.
My best guess is that the
__iadd__
is invokingextend
, 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.
This is the correct answer for why this happens. I've found the relevant docs that say this-
In the docs you can see that in fact __iadd__
is equivalent to .extend()
, and here it says:
list.extend(iterable)
: Extend the list by appending all the items from the iterable.
In the part about dicts it says:
Performing
list(d)
on a dictionary returns a list of all the keys used in the dictionary
So to summarize, a_list += a_dict
is equivalet to a_list.extend(iter(a_dict))
, which is equivalent to a_list.extend(a_dict.keys())
, which will extend the list with the list of keys in the dictionary.
We can maybe discuss on why this is the way things are, but I don't think we will find a clear-cut answer. I think +=
is a very useful shorthand for .extend
, and also that a dictionary should be iterable (personally I'd prefer it returning .items()
, but oh well)
Edit: You seem to be interested in the actual implementation of CPython, so here are some code pointers:
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);
...
}
and then this method seems to be referenced above inside a PySequenceMethods
struct, which seems to be an abstraction of sequences that defines common actions such as concatenating in-place, and concatenating normally (which is defined as list_concat
in the same file and you can see is not the same).