This is and always has been a problem with mutability in general, and operator overloading specifically. C++ is no better.
The expression a + b
computes a new list from the objects bound to a
and b
, which are not modified. When you assign this back to a
, you change the binding of one variable to point to the new value. It is expected that +
is symmetrical, so you can't add a dict and a list.
The statement a += b
modifies the existing list bound to a
. Since it does not change the object identity, the changes are visible to all bindings to the object represented by a
. The operator +=
is obviously not symmetrical, it is equivalent to list.extend
, which iterates over the second operand. For dictionaries, this means listing the keys.
Discussion:
If an object doesn't implement +=
, then Python will translate it into an equivalent statement using +
and =
. So the two are sometimes equivalent, depending on the type of the objects involved.
The benefit of a +=
that mutates the referand (as opposed to the operand value, which is a reference) is that the implementation can be more efficient without a corresponding increase in implementation complexity.
In other languages, you might use more obvious notation. For example, in a hypothetical version of Python with no operator overloading, you might see:
a = concat(a, b)
versus
a.extend(a, b)
The operator notation is really just shorthand for these.
Bonus:
Try it with other iterables too.
>>> 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']
It's useful to be able to do this, because you can append a generator to a list with +=
and get the generator contents. It's unfortunate that it breaks compatibility with +
, but oh well.