2

The following Python code:

a = 1
b = 2
a, b = b, a

compiles to the following Python bytecode:

  1           0 LOAD_CONST               0 (1)
              2 STORE_NAME               0 (a)

  2           4 LOAD_CONST               1 (2)
              6 STORE_NAME               1 (b)

  3           8 LOAD_NAME                1 (b)
             10 LOAD_NAME                0 (a)
             12 ROT_TWO
             14 STORE_NAME               0 (a)
             16 STORE_NAME               1 (b)
             18 LOAD_CONST               2 (None)
             20 RETURN_VALUE

According to Python's docs, the instruction ROT_TWO swaps the order of the first two elements of the stack.

I'm having a hard time understanding why this is necessary.

My understanding is that, in order to swap a and b, you could just push a, then push b, then store the stacktop in a, then store the stacktop in b, achieving the variable swap. Something like this:

 1           0 LOAD_CONST               0 (1)
              2 STORE_NAME               0 (a)

  2           4 LOAD_CONST               1 (2)
              6 STORE_NAME               1 (b)

  3           8 LOAD_NAME                1 (b)
             10 LOAD_NAME                0 (a)
             14 STORE_NAME               1 (b)
             16 STORE_NAME               0 (a)
             18 LOAD_CONST               2 (None)
             20 RETURN_VALUE

Why is this extra operation needed? Is there any advantage to doing it over the solution I suggested?

Samuele B.
  • 481
  • 1
  • 6
  • 29
  • 1
    Will you write out the bytecode that you describe in your next-to-last paragraph? – Code-Apprentice Oct 26 '22 at 22:02
  • I believe it's to guarantee that each side is evaluated from left to right. See https://stackoverflow.com/a/21047622/3799759. – Samwise Oct 26 '22 at 22:05
  • The extra operation isn't needed here, where everything is a simple variable - but if `a` and `b` were actually something more complicated, that had side effects (`x[func1()], x[func2()]`, for example), the simplification would result in those side effects happening in a different order. – jasonharper Oct 26 '22 at 22:05
  • 2
    [evaluation order](https://docs.python.org/3/reference/expressions.html#evaluation-order) - right-hand-side first (left to right) then left-hand-side (left to right) – wwii Oct 26 '22 at 22:11
  • The evaluation order needs to be consistent and predictable - as mentioned in a comment below - `..swapping is just a special case of a larger idiom` - so it requires the extra operation to conform to the language spec. I've seen Q&A's here on SO regarding subtle things that happen with assignments because of the evaluation order. – wwii Oct 26 '22 at 22:24

1 Answers1

2

My understanding is that, in order to swap a and b, you could just push a, then push b, then store the stacktop in a, then store the stacktop in b, achieving the variable swap.

You could, but that would read a first. a, b = b, a says to read b first.

That usually doesn't matter, but it can if you're executing this code in a very weird namespace, plus it would have implications for thread-safety and debugging.

user2357112
  • 260,549
  • 28
  • 431
  • 505
  • Is it possible for threads to do this operation wrong in python, considering how threads still need the GIL? – Tom McLean Oct 26 '22 at 22:08
  • @TomMcLean: The GIL protects the interpreter's internals from memory corruption. It doesn't automatically make Python code thread-safe. Another thread could still intervene between the variable read operations. – user2357112 Oct 26 '22 at 22:09
  • 2
    If either of the things on the right of the equals are function calls with side effects, execution order will matter even without threading troubles. Remember that swapping is just a special case of a larger idiom. – Mark Ransom Oct 26 '22 at 22:13
  • I think my understanding of threads in python is wrong then, I always assumed that multithreading in python does not run concurrently, so the operation `a, b = b, a` will finish before another thread can do anything. Unless a and b were doing something weird like releasing the GIl during variable assignment. Is my understanding incorrect? – Tom McLean Oct 26 '22 at 22:14
  • I guess like what @MarkRansom says, if b was `do_some_thread_task()` and a was another threading task, something weird could happen? – Tom McLean Oct 26 '22 at 22:15
  • 2
    @TomMcLean: The GIL does not make `a, b = b, a` atomic. It will guarantee that, for example, threads won't intervene halfway through a variable read or write, but it won't guarantee that an entire line of Python code executes uninterrupted. – user2357112 Oct 26 '22 at 22:28
  • 1
    @MarkRansom: Yeah, but if the reordering were safe for the special case of a variable swap, the peephole optimizer could have a case to optimize this. (It already optimizes out the tuple packing and unpacking that `a, b = b, a` would perform if executed literally as written.) – user2357112 Oct 26 '22 at 22:37