Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Tuples: += operator throws exception, but succeeds? [duplicate]

Tags:

python

tuples

Why does the following throw an exception, although it succeeds?

>>> t = ([1, 2, 3], 4)
>>> t[0] += [1]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> t
([1, 2, 3, 1], 4)
>>> 
like image 746
Nehal J Wani Avatar asked Jul 13 '16 06:07

Nehal J Wani


People also ask

Is duplicate allowed in tuple?

You can directly enter duplicate items in a Python tuple as it doesn't behave like a set(which takes only unique items).

Is repetition allowed in tuple in Python?

The repetition operator makes multiple copies of a tuple and joins them all together. Tuples can be created using the repetition operator, *.

How do you duplicate a tuple?

copy. copy() and copy. deepcopy() just copy the reference for an immutable object like a tuple.

Can tuples be overwritten?

Once a tuple is created, you cannot change its values. Tuples are unchangeable, or immutable as it also is called.


2 Answers

Found the answer on IRC.

t[0] += [1] is several discrete actions:

  1. loading t[0]
  2. building a new list with 1 in it
  3. adding that [1] to whatever t[0] is
  4. reassigning t[0]

It seems that x += y is basically x = x + y (but, is it?)

The tricky bit is that += implies assignment to both the tuple t and to the list t[0]

t[0] += [1] is not literally t[0] = t[0] + [1], it is: t[0] = t[0].__iadd__([1])

What really happens is:

  1. __iadd__ both mutates the list and returns it. So the list (which is the first element in t) has already got 1 appended to it.
  2. tuple's mutation is attempted in-place as well, but tuples are immutable, resulting in the exception.

Why is this not visible in plain sight? Because a n00b like me would expect t[0] += [1] to either succeed all together or fail, because it's one short line of python. But that's not always the case.

like image 100
Nehal J Wani Avatar answered Sep 28 '22 02:09

Nehal J Wani


It can also help to understand this behavior by taking a look at the bytecode with dis.dis.

In[5]: dis('t[0] += [1]')
  1           0 LOAD_NAME                0 (t)
              3 LOAD_CONST               0 (0)
              6 DUP_TOP_TWO
              7 BINARY_SUBSCR
              8 LOAD_CONST               1 (1)
             11 BUILD_LIST               1
             14 INPLACE_ADD
             15 ROT_THREE
             16 STORE_SUBSCR
             17 LOAD_CONST               2 (None)
             20 RETURN_VALUE
  • The value of t[0] is placed on top of the stack with BINARY_SUBSCR, which is a (mutable) list in this case.
  • The value on the top of the stack has += [1] performed on it with INPLACE_ADD, where in this case the top of the stack refers to the list inside the tuple.
  • The assigning of t[0] to the top of the stack occurs with STORE_SUBSCR, which fails here as t itself is an immutable tuple, raising the error after the += assignment has already occurred.
like image 29
miradulo Avatar answered Sep 28 '22 03:09

miradulo