If we look at the instructions the compiler generates using the dissasembler:
>>> import dis
>>> dis.dis("A[A.index(a)],A[A.index(b)]=A[A.index(b)],A[A.index(a)]")
1 0 LOAD_NAME 0 (A)
2 LOAD_NAME 0 (A)
4 LOAD_METHOD 1 (index)
6 LOAD_NAME 2 (b)
8 CALL_METHOD 1
10 BINARY_SUBSCR
12 LOAD_NAME 0 (A)
14 LOAD_NAME 0 (A)
16 LOAD_METHOD 1 (index)
18 LOAD_NAME 3 (a)
20 CALL_METHOD 1
22 BINARY_SUBSCR
24 ROT_TWO
26 LOAD_NAME 0 (A)
28 LOAD_NAME 0 (A)
30 LOAD_METHOD 1 (index)
32 LOAD_NAME 3 (a)
34 CALL_METHOD 1
36 STORE_SUBSCR
38 LOAD_NAME 0 (A)
40 LOAD_NAME 0 (A)
42 LOAD_METHOD 1 (index)
44 LOAD_NAME 2 (b)
46 CALL_METHOD 1
48 STORE_SUBSCR
50 LOAD_CONST 0 (None)
52 RETURN_VALUE
>>>
So, first, it evaluates A[A.index(b)],A[A.index(a)]
, we can think of the partially evaluated statement as:
A[A.index(a)], A[A.index(b)]= 1, 5
Then, as you can see, the first part
A[0] = 1
Gets done first, i.e.
26 LOAD_NAME 0 (A)
28 LOAD_NAME 0 (A)
30 LOAD_METHOD 1 (index)
32 LOAD_NAME 3 (a)
34 CALL_METHOD 1
36 STORE_SUBSCR
So the list has:
[1, 1, 3, 4, 2]
Then, the final part,
38 LOAD_NAME 0 (A)
40 LOAD_NAME 0 (A)
42 LOAD_METHOD 1 (index)
44 LOAD_NAME 2 (b)
46 CALL_METHOD 1
48 STORE_SUBSCR
But at this point, A.index(b)
is 0
! So it does:
A[0] = 5
And you end up where you started. The problem is, you modified where A.index(b)
would end up, since it happens from left-to-right in order.
Here is a reference to the documentation for assignment statements in the language spec. I guess the important thing to note is that the right-hand side is evaluated first, then the parts of the left-hand side (potentially separated by commas) are evaluated one by one in order.