Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mutating a list inside a tuple in Python [duplicate]

I'm a bit confused about modifying tuple members. The following doesn't work:

>>> thing = (['a'],)
>>> thing[0] = ['b']
TypeError: 'tuple' object does not support item assignment
>>> thing
(['a'],)

But this does work:

>>> thing[0][0] = 'b'
>>> thing
(['b'],)

Also works:

>>> thing[0].append('c')
>>> thing
(['b', 'c'],)

Doesn't work, and works (huh?!):

>>> thing[0] += 'd'
TypeError: 'tuple' object does not support item assignment
>>> thing
(['b', 'c', 'd'],)

Seemingly equivalent to previous, but works:

>>> e = thing[0]
>>> e += 'e'
>>> thing
(['b', 'c', 'd', 'e'],)

So what exactly are the rules of the game, when you can and can't modify something inside a tuple? It seems to be more like prohibition of using the assignment operator for tuple members, but the last two cases are confusing me.

like image 511
wim Avatar asked Feb 07 '12 07:02

wim


3 Answers

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']
like image 97
Martin Geisler Avatar answered Nov 08 '22 15:11

Martin Geisler


You can't modify the tuple, but you can modify the contents of things contained within the tuple. Lists (along with sets, dicts, and objects) are a reference type and thus the "thing" in the tuple is just a reference - the actual list is a mutable object which is pointed to by that reference and can be modified without changing the reference itself.

( + ,)       <--- your tuple (this can't be changed)
  |
  |
  v
 ['a']       <--- the list object your tuple references (this can be changed)

After thing[0][0] = 'b':

( + ,)       <--- notice how the contents of this are still the same
  |
  |
  v
 ['b']       <--- but the contents of this have changed

After thing[0].append('c'):

( + ,)       <--- notice how this is still the same
  |
  |
  v
 ['b','c']   <--- but this has changed again

The reason why += errors is that it's not completely equivalent to .append() - it actually does an addition and then an assignment (and the assignment fails), rather than merely appending in-place.

like image 42
Amber Avatar answered Nov 08 '22 14:11

Amber


You cannot replace an element of a tuple, but you can replace the entire contents of the element. This will work:

thing[0][:] = ['b']
like image 36
Lawrence D'Oliveiro Avatar answered Nov 08 '22 14:11

Lawrence D'Oliveiro