List mutation in Python

I tried to modify the list by replacing the common item between the list and another list of links with the first item. The implementation is done as shown below:

>>> L = [1,2,3,4,5,6,7,8,9]
>>> A = [3]
>>> L[0], L[L.index(A[0])] = L[L.index(A[0])], L[0] #want to swap 3 with 1  
>>> L 
[1,2,3,4,5,6,7,8,9,] #List L was not mutated  

      

The list has not been mutated as I expected. But when I change the implementation like below it works:

>>> L = [1,2,3,4,5,6,7,8,9]
>>> A = [3]
>>> i = L.index(A[0])
>>> L[0], L[i] = L[i], L[0]
>>> L
[3,2,1,4,5,6,7,8,9,] #Now list mutated as desired even though L[i] and L[L.index(A[0])] evaluate to same value.  

      

My question is, why couldn't the first assignment change the list? I thought about it, but my brain couldn't explain it.

+3


source to share


2 answers


The problem is they are not equivalent. The first example is akin to:

>>> L = [1,2,3,4,5,6,7,8,9]
>>> A = [3]
>>> i = L.index(A[0])
>>> L[0] = L[i]
>>> i = L.index(A[0])
>>> L[i] = L[0]

      

This means that you end up swapping and then finding the element that you just swapped and replaced.



The reason you are confused is because you are thinking about assigning a tuple, since Python does both things at the same time - this is not how the execution is done, it is done in an order that changes the result.

It's worth noting that even if it worked, it wouldn't be the optimal way to do it. list.index()

is not particularly fast, so doing it twice for no reason is not a great idea.

+6


source


Although in Python the right-hand side is first evaluated when making multiple assignments, left-hand targets, if they have expressions in them, are evaluated one by one when assigned.

If instead they are evaluated as assignment targets at first, as you seem to expect, this will of course work.

This is described in the assignment instructions section :

The assignment operator evaluates a list of expressions (remember that it can be a single expression or a comma-separated list, the last of which has a tuple) and assigns a single resulting object to each of the target lists from left to right.

and

If the target list is a comma-separated list of targets: the object must be iterable with the same number of items as in the target list, and items are assigned from left to right, matching the target.

My accent. The left-right is important here. L[0]

assigned prior to appointment L[L.index(3)]

.

The documentation further details what happens to the targeted subscription, for example L[0]

and L[L.index(3)]

:



If the target is a subscription: the main expression in the link is evaluated. It must give either a mutable sequence object (like a list) or a mapping object (like a dictionary). The index expression is then evaluated.

Again, my blow; substring expression is evaluated separately, and since the target list is evaluated from left to right, this evaluation is performed after the previous assignment to L[0]

.

This can be seen by parsing the python code:

>>> import dis
>>> def f(L):
...     L[0], L[2] = L[2], L[0]
... 
>>> def g(L):
...     L[0], L[L.index(3)] = L[L.index(3)], L[0]
... 
>>> dis.dis(f)
  2           0 LOAD_FAST                0 (L)   # L[2]
              3 LOAD_CONST               1 (2)
              6 BINARY_SUBSCR       
              7 LOAD_FAST                0 (L)   # L[0]
             10 LOAD_CONST               2 (0)
             13 BINARY_SUBSCR       
             14 ROT_TWO             
             15 LOAD_FAST                0 (L)   # Store in L[0]
             18 LOAD_CONST               2 (0)
             21 STORE_SUBSCR        
             22 LOAD_FAST                0 (L)   # Store in L[2]
             25 LOAD_CONST               1 (2)
             28 STORE_SUBSCR        
             29 LOAD_CONST               0 (None)
             32 RETURN_VALUE        
>>> dis.dis(g)
  2           0 LOAD_FAST                0 (L)   # L[L.index(3)]
              3 LOAD_FAST                0 (L)
              6 LOAD_ATTR                0 (index)
              9 LOAD_CONST               1 (3)
             12 CALL_FUNCTION            1
             15 BINARY_SUBSCR       
             16 LOAD_FAST                0 (L)  #  L[0]
             19 LOAD_CONST               2 (0)
             22 BINARY_SUBSCR       
             23 ROT_TWO             
             24 LOAD_FAST                0 (L)  # Store in L[0]
             27 LOAD_CONST               2 (0)
             30 STORE_SUBSCR        
             31 LOAD_FAST                0 (L)  # Store in L[L.index(3)]
             34 LOAD_FAST                0 (L)
             37 LOAD_ATTR                0 (index)
             40 LOAD_CONST               1 (3)
             43 CALL_FUNCTION            1
             46 STORE_SUBSCR        
             47 LOAD_CONST               0 (None)
             50 RETURN_VALUE        

      

The save operation first saves L[0] = 3

, so the next call L.index(3)

returns 0

, and 1

thus saves back to position 0

!

The following is done:

L[L.index(3)], L[0] = L[0], L[L.index(3)]

      

because now the search L.index(3)

is done first. However, it is best to store the result of the call .index()

in a temporary variable, since not calling .index()

twice will be more efficient.

+12


source







All Articles