You can always modify a mutable value inside a tuple. The puzzling behavior you see with
>>> thing[0] += 'd'
is caused by +=
. The +=
operator does in-place addition but also an assignment — the in-place addition works just file, but the assignment fails since the tuple is immutable. Thinking of it like
>>> thing[0] = thing[0] + 'd'
explains this better. We can use the dis
module from the standard library to look at the bytecode generated from both expressions. With +=
we get an INPLACE_ADD
bytecode:
>>> def f(some_list):
... some_list += ["foo"]
...
>>> dis.dis(f)
2 0 LOAD_FAST 0 (some_list)
3 LOAD_CONST 1 ('foo')
6 BUILD_LIST 1
9 INPLACE_ADD
10 STORE_FAST 0 (some_list)
13 LOAD_CONST 0 (None)
16 RETURN_VALUE
With +
we get a BINARY_ADD
:
>>> def g(some_list):
... some_list = some_list + ["foo"]
>>> dis.dis(g)
2 0 LOAD_FAST 0 (some_list)
3 LOAD_CONST 1 ('foo')
6 BUILD_LIST 1
9 BINARY_ADD
10 STORE_FAST 0 (some_list)
13 LOAD_CONST 0 (None)
16 RETURN_VALUE
Notice that we get a STORE_FAST
in both places. This is the bytecode that fails when you try to store back into a tuple — the INPLACE_ADD
that comes just before works fine.
This explains why the "Doesn't work, and works" case leaves the modified list behind: the tuple already has a reference to the list:
>>> id(thing[0])
3074072428L
The list is then modified by the INPLACE_ADD
and the STORE_FAST
fails:
>>> thing[0] += 'd'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
So the tuple still has a reference to the same list, but the list has been modified in-place:
>>> id(thing[0])
3074072428L
>>> thing[0]
['b', 'c', 'd']