2

I am new to Python and the following question is so difficult for me to understand.

a,b,c=1,2,3
a,b,c=c,a,b=b,a,c=c,b,a
print(a,b,c)

The output of this is -

(2,3,1)

but I don't understand why it isn't -

(3,2,1)
James Z
  • 12,209
  • 10
  • 24
  • 44
  • 1
    `(a, b, c) = (c, a, b) = (b, a, c) = (3, 2, 1)`, and while keeping in mind that statements are evaluated from right to left, is it clearer now? – DeepSpace Dec 26 '19 at 18:24
  • This would be a prefect time for you to use a debugger! debuggers are a great tool especially if you can use them properly. If you're using PyCharm press `alt+shift+f9` and use `alt+f7` to step through. – BpY Dec 26 '19 at 21:08

3 Answers3

4

What you think it is happening

a, b, c = 1, 2, 3
b, a, c = c, b, a
print(a, b, c)
# 2, 3, 1
c, a, b = b, a, c
print(a, b, c)
# 2, 1, 3
a, b, c = c, a, b
print(a, b, c)
# 3, 2, 1

What is actually happening

a, b, c = 1, 2, 3
# a, b, c = c, a, b = b, a, c = c, b, a
temp = c, b, a
a, b, c = temp
print(a, b, c)
# 3 2 1
c, a, b = temp
print(a, b, c)
# 2 1 3
b, a, c = temp
print(a, b, c)
# 2 3 1

Basically: the loading happen from the right according to c, b, a, while the storing happen from left to right.

This is evidenced by disassembling the expression:

import dis


def chained_assign():
    a, b, c = 1, 2, 3
    a, b, c = c, a, b = b, a, c = c, b, a
    return a, b, c


dis.dis(chained_assign)

Output:

  5           0 LOAD_CONST               4 ((1, 2, 3))
              2 UNPACK_SEQUENCE          3
              4 STORE_FAST               0 (a)
              6 STORE_FAST               1 (b)
              8 STORE_FAST               2 (c)

  6          10 LOAD_FAST                2 (c)
             12 LOAD_FAST                1 (b)
             14 LOAD_FAST                0 (a)
             16 BUILD_TUPLE              3
             18 DUP_TOP
             20 UNPACK_SEQUENCE          3
             22 STORE_FAST               0 (a)
             24 STORE_FAST               1 (b)
             26 STORE_FAST               2 (c)
             28 DUP_TOP
             30 UNPACK_SEQUENCE          3
             32 STORE_FAST               2 (c)
             34 STORE_FAST               0 (a)
             36 STORE_FAST               1 (b)
             38 UNPACK_SEQUENCE          3
             40 STORE_FAST               1 (b)
             42 STORE_FAST               0 (a)
             44 STORE_FAST               2 (c)

  7          46 LOAD_FAST                0 (a)
             48 LOAD_FAST                1 (b)
             50 LOAD_FAST                2 (c)
             52 BUILD_TUPLE              3
             54 RETURN_VALUE

Notice the order of the STORE_FAST and LOAD_FAST instructions from line 6.

Additional discussion here:

norok2
  • 25,683
  • 4
  • 73
  • 99
1

It is because, when you write like this it means -: a = c = b = c value of a becomes c, the value of c becomes b and the value of b becomes c. so in last the change in the variable is taking place for b not a.

so the output would be-:

2 3 1

not-:

3 2 1
Rudra shah
  • 804
  • 8
  • 10
1

As norok indicated, storage and loading happen from different directions. If we take the following code we can see what python is doing under the hood.

import dis

def foo():
    a, b, c = 1, 2, 3
    a, b, c = c, a, b = b, a, c = c, b, a

dis.dis(foo)

Below you see the bytecode. To the right of the comments, you see the values for variables a, b, and c as well as the memory stack at the end of the operation. You'll see the DUP_TOP command negate the assignments at various steps so only the first load and last store appear to do anything. This would also explain why a, b, c = a, a, a = b, b, b = c, c, c = b, a, c = c, b, a still evaluates to (2, 3, 1).

                                                      # a b c stack

4           0 LOAD_CONST               4 ((1, 2, 3))  # - - - [(1, 2, 3)]
            3 UNPACK_SEQUENCE          3              # - - - [1, 2, 3]
            6 STORE_FAST               0 (a)          # 1 - - [2, 3]
            9 STORE_FAST               1 (b)          # 1 2 - [3]
           12 STORE_FAST               2 (c)          # 1 2 3 []

5          15 LOAD_FAST                2 (c)          # 1 2 3 [3]
           18 LOAD_FAST                1 (b)          # 1 2 3 [3, 2]
           21 LOAD_FAST                0 (a)          # 1 2 3 [3, 2, 1]
           24 BUILD_TUPLE              3              # 1 2 3 [(3, 2, 1)]
           27 DUP_TOP                                 # 1 2 3 [(3, 2, 1), (3, 2, 1)]
           28 UNPACK_SEQUENCE          3              # 1 2 3 [3, 2, 1, (3, 2, 1)]
           31 STORE_FAST               0 (a)          # 3 2 3 [2, 1, (3, 2, 1)]
           34 STORE_FAST               1 (b)          # 3 2 3 [1, (3, 2, 1)]
           37 STORE_FAST               2 (c)          # 3 2 1 [(3, 2, 1)]
           40 DUP_TOP                                 # 3 2 1 [(3, 2, 1), (3, 2, 1)]
           41 UNPACK_SEQUENCE          3              # 3 2 1 [3, 2, 1, (3, 2, 1)]
           44 STORE_FAST               2 (c)          # 3 2 3 [2, 1, (3, 2, 1)]
           47 STORE_FAST               0 (a)          # 2 2 3 [1, (3, 2, 1)]
           50 STORE_FAST               1 (b)          # 2 1 3 [(3, 2, 1)]
           53 UNPACK_SEQUENCE          3              # 2 1 3 [3, 2, 1]
           56 STORE_FAST               1 (b)          # 2 3 3 [2, 1]
           59 STORE_FAST               0 (a)          # 2 3 3 [1]
           62 STORE_FAST               2 (c)          # 2 3 1 []
Cohan
  • 4,384
  • 2
  • 22
  • 40
  • `a, b, c = a, a, a = b, b, b = c, c, c = b, a, c = c, b, a` is still `(2, 3, 1)` and this logic does not explain it – norok2 Dec 26 '19 at 20:34
  • Perhaps [this](https://stackoverflow.com/a/59493010/5218354) helps ;-) – norok2 Dec 26 '19 at 21:11
  • 1
    Yeah, I was just working through the bytecode and realized that `DUP_TOP` is what carried the values through. That was my first time digging through the bytecode like that, so thanks for the nudge. – Cohan Dec 26 '19 at 21:14