A comment on https://nedbatchelder.com/text/names.html
Myth: Python assigns mutable and immutable values differently.
While it is true that Python's assignment semantics are the same for both mutable and immutable types, the results look different for augmented assignments. E.g. the code
a = [1, 2, 3]
b = a
a += 4,
creates a new list, points both a
and b
to that list, and then appends another
element. After this snippet ran, there is still only a single list, pointed to by
both a
and b
. If you do the same with tuples, though
a = 1, 2, 3
b = a
a += 4,
you end up with two tuples: a
is (1, 2, 3, 4)
, while b
is still (1, 2, 3)
.
So augmented assignments behave differently for lists and tuples. This difference
is not caused by different code being generated in both cases, but rather by a
difference in the implementations of list.__iadd__()
and tuple.__iadd__()
.
The line a += 4,
generates code that is equivalent to
a = type(a).__iadd__(a, (4,))
for both the first and the second example. The implementation of __iadd__()
for
a list modifies the list in place and then returns a reference to the list that was
passed in, so the assignment does not change the value a
points to. For tuples,
on the other hand, __iadd__()
creates and returns a new tuple, so the augmented
assignments results in an actual reassignment of the left-hand side.
Things get really crazy if you try augmented assignments of mutable elements in a tuple:
>>> a = [],
>>> a[0] += [1]
Traceback (most recent call last):
File "<ipython-input-13-ea29ca190a4d>", line 1, in <module>
a[0] += [1]
TypeError: 'tuple' object does not support item assignment
>>> a
([1],)
This code appends to a list embedded in a tuple, which should be possible, since a list is mutable. And the augmented assignment indeed modifies the list in place, but you still get an exception. If you look at the equivalent code the augmented assignment gets translated to, it's easy to understand why:
a[0] = type(a[0]).__iadd__(a[0], [1])
All of this is also why using mutable arguments for functions is generally a Bad Idea. Names are local to the current scope, by default, but values are global.
So when you write something like this:
the value is created when evaluating the function definition, and is kept around for later use when the function is called. So, this is what you get:
Each time the function is called, we get a new namespace, but the backend value for
value
(the default value for the name if not otherwise defined) sticks around.This does let us have some fun times with dynamic programming and implicit state passing: