a += b not the same as a = a + b

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.


The reason behind this is because the python lists (a in your case) implement the __iadd__ method, which in turns calls the __iter__ method on the passed parameter.

The following code snippet illustrates this better:

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

The result is:

__iadd__ was called
__iter__ was called
['a', 'b', 'c', 'd2', 'd3', 'd1']

The python interpreter checks if an object implements the __iadd__ operation (+=) and only if it doesn't it will emulate it by doing a add operation followed by an assignment.

Tags:

Python