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 работает внутри? Он определенно не использует временную переменную.
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 работает внутри? Он определенно не использует временную переменную.
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
для эффективности.