Most people understand the difference between a tuple and a list by saying that a tuple is an immutable sequence that cannot be assigned to its elements. That’s how I understood it. Here’s an example:

In : a = (1, 2, 3)
In : a[3] = 4
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-5-d840230b1ac3> in <module>()
----> 1 a[3] = 4
TypeError: 'tuple' object does not support item assignment
In : a
Out: (1, 2, 3)Copy the code

A tuple is generated, and its elements can’t change anymore.

But I’m sure many of you have seen the following (which some consider a Python joke) :

In : a = (1, 2, [3, 4])
In : a[2] += [5, 6]
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-2-84fb4a701b92> in <module>()
----> 1 a[2] += [5, 6]
TypeError: 'tuple' object does not support item assignment
In : a
Out: (1, 2, [3, 4, 5, 6])Copy the code

Explicitly wrong, but still changed for the value of A?

I once thought about this problem and felt it was “the assignment to list [3, 4] was successful, but the tuple assignment failed later”, but I had no evidence. It wasn’t until I read Fluent Python last night that I got a positive answer from the author. Today we use the dis module to parse bytecodes generated by += :

In : import dis
In : a = (1, 2, [3, 4])
In : dis.dis('a[2] += [5, 6]')
  1           0 LOAD_NAME                0 (a)
              2 LOAD_CONST               0 (2)
              4 DUP_TOP_TWO
              6 BINARY_SUBSCR
              8 LOAD_CONST               1 (5)
             10 LOAD_CONST               2 (6)
             12 BUILD_LIST               2
             14 INPLACE_ADD
             16 ROT_THREE
             18 STORE_SUBSCR
             20 LOAD_CONST               3 (None)
             22 RETURN_VALUECopy the code

There seems to be a bunch of instructions. Let me explain them step by step:

  1. LOAD_NAME. The associated value of the local variable (that is, a) is placed on the stack.

  2. LOAD_CONST. The corresponding constant used in the bytecode (that is, 2) is placed on the stack.

  3. DUP_TOP_TWO. Copy the first two references at the top of the stack (that is, a and 2) and preserve their order.

  4. BINARY_SUBSCR. Put a[2] at the top of the stack.

  5. LOAD_CONST. Put 5 and 6 on the stack, respectively.

  6. BUILD_LIST. Create a list based on the number currently contained on the stack and put it on the stack.

  7. INPLACE_ADD. A += b is essentially the same thing as a = a + b, which is an in-place add to the top of the stack.

  8. ROT_THREE. Raise the second and third positions in the stack, and lower the top (i.e., [3, 4, 5, 6]) to the third position in the stack.

  9. STORE_SUBSCR. A [2] = [3, 4, 5, 6]. But because the tuple is immutable, this step fails.

The iadd operation was successfully performed on the list, and the tuple assignment failed.

That is:

x = a[2] 
x = x.__iadd__([5, 6])
a[2] = xCopy the code

Like this. Validation:

In : a = (1, 2, [3, 4])
In : a[2] = [3, 4, 5, 6]
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-31-d5ba6baf4cf6> in <module>()
----> 1 a[2] = [3, 4, 5, 6]
TypeError: 'tuple' object does not support item assignment
In : a
Out: (1, 2, [3, 4])Copy the code

You can see that the direct assignment did not succeed.

In Python, variable assignments take the form of object references, passing the memory address of an object (like a pointer). In this case, the a entries refer to the entities in memory that hold different data, and the modification of the list entities will succeed:

In : b = [3, 4]
In : a = (1, 2, b)
In : id(b)
Out: 4571378504
In : a[2] += [5, 6]
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-16-84fb4a701b92> in <module>()
----> 1 a[2] += [5, 6]
TypeError: 'tuple' object does not support item assignment
In : id(b)
Out: 4571378504
In : a
Out: (1, 2, [3, 4, 5, 6])Copy the code

You can see that b is the same object as it was when it was changed. But changes to other items are not successful:

In : a[1] += 1
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-25-9fac1c91b625> in <module>()
----> 1 a[1] += 1
TypeError: 'tuple' object does not support item assignment
In : a
Out: (1, 2, [3, 4, 5, 6])Copy the code

This is because numbers and strings are immutable objects. And dictionaries can also be modified successfully:

In : a = (1, 2, {'b': 1})
In : a[2]['b'] += 3
In : a
Out: (1, 2, {'b': 4})Copy the code

It worked without any errors. Let’s assign directly:

In : a[2] = {'b': 5}
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-36-a2916525c596> in <module>()
----> 1 a[2] = {'b': 5}
TypeError: 'tuple' object does not support item assignment
In : a
Out: (1, 2, {'b': 4})Copy the code

So a[2][‘b’] += 3 is not an assignment to a tuple, but a direct operation on the dictionary item in the tuple. Feeling:

                                                            In : a = (1, 2, {'b': 1})
In : dis.dis("a[2]['b'] += 5")
          0 STORE_GLOBAL    12891 (12891)
          3 FOR_ITER        10075 (to 10081)
          6 DELETE_GLOBAL   23847 (23847)
          9 SLICE+2
         10 STORE_SLICE+3
         11 DELETE_SUBSCR
         12 SLICE+2
         13 DELETE_SLICE+3
In : c = a[2]
In : c
Out: {'b': 1}
In : dis.dis("c['b'] += 5")
          0 DUP_TOPX        10075
          3 DELETE_GLOBAL   23847 (23847)
          6 SLICE+2
          7 STORE_SLICE+3
          8 DELETE_SUBSCR
          9 SLICE+2
         10 DELETE_SLICE+3
Copy the code

See, C is a dict, and the bytecode instructions for c[‘b’] += 5″ are the same as most of the following instructions for a[2][‘b’] += 5.