Как происходит переопределение элементов в кортежах python (a, b) = (b, a)?

In [55]: a = 5

In [56]: b = 6

In [57]: (a, b) = (b, a)

In [58]: a
Out[58]: 6

In [59]: b
Out[59]: 5

Как эта замена значений a и b работает внутри? Он определенно не использует временную переменную.

Ответ 1

Python отделяет правое выражение от назначения левой стороны. Сначала оценивается правая часть, и результат сохраняется в стеке, а затем имена левой стороны назначаются с использованием кодов операций, которые снова принимают значения из стека.

Для назначений кортежей с 2 ​​или 3 элементами Python просто использует стек напрямую:

>>> import dis
>>> def foo(a, b):
...     a, b = b, a
... 
>>> dis.dis(foo)
  2           0 LOAD_FAST                1 (b)
              3 LOAD_FAST                0 (a)
              6 ROT_TWO             
              7 STORE_FAST               0 (a)
             10 STORE_FAST               1 (b)
             13 LOAD_CONST               0 (None)
             16 RETURN_VALUE        

После двух LOAD_FAST opcodes (которые выталкивают значение из переменной в стек) верхняя часть стека содержит [a, b], Оператор ROT_TWO заменяет верхние две позиции в стеке, поэтому стек теперь имеет [b, a] вверху. Два STORE_FAST opcodes затем берут эти два значения и сохраняют их в именах в левой части задания. Первая STORE_FAST выводит значение вершины стека и помещает ее в a, затем снова появляется сообщение, сохраняя значение в b. Вращение необходимо, потому что Python гарантирует, что назначения в целевом списке с левой стороны выполняются слева направо.

Для назначения трех имен ROT_THREE, за которым следует ROT_TWO, выполняется обращение к трем верхним элементам в стеке.

Для более длинных левосторонних назначений строит явный кортеж:

>>> def bar(a, b, c, d):
...     d, c, b, a = a, b, c, d
... 
>>> dis.dis(bar)
  2           0 LOAD_FAST                0 (a)
              3 LOAD_FAST                1 (b)
              6 LOAD_FAST                2 (c)
              9 LOAD_FAST                3 (d)
             12 BUILD_TUPLE              4
             15 UNPACK_SEQUENCE          4
             18 STORE_FAST               3 (d)
             21 STORE_FAST               2 (c)
             24 STORE_FAST               1 (b)
             27 STORE_FAST               0 (a)
             30 LOAD_CONST               0 (None)
             33 RETURN_VALUE        

Здесь стек с [d, c, b, a] используется для построения кортежа (в обратном порядке BUILD_TUPLE снова появляется из стека, нажав результирующий кортеж в стек), а затем UNPACK_SEQUENCE снова выставляет кортеж из стека, отбрасывает все элементы из кортежа обратно на стека снова для операций STORE_FAST.

Последний может показаться расточительной операцией, но правая часть задания может быть чем-то совершенно другим, вызов функции, который создает кортеж, возможно, поэтому интерпретатор Python не делает предположений и использует код операции UNPACK_SEQUENCE всегда. Он делает это даже для операций присваивания двух и трех имен, но шаг оптимизации (глазок) заменяет BUILD_TUPLE/UNPACK_SEQUENCE сочетание с 2 или 3 аргументами с приведенными выше кодами ROT_TWO и ROT_THREE для эффективности.