You can always modify a mutable value inside a tuple. The puzzling behavior you see with
>>> thing += 'd'
is caused by
+= 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 = thing + '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
>>> 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
+ we get a
>>> 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) 3074072428L
The list is then modified by the
INPLACE_ADD and the
>>> thing += '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) 3074072428L >>> thing ['b', 'c', 'd']