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は、右側の式を左側の割り当てから分離します。最初に右側が評価され、結果がスタックに格納されます。次に、スタックに値fromをとるオペコードを使用して左側の名前が再度割り当てられます。
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
2つの LOAD_FAST
opcodes (変数からスタックに値をプッシュする)の後、スタックのトップは[a, b]
を保持します。 ROT_TWO
opcode は、スタックの上部の2つの位置を入れ替えるため、スタックの上部に[b, a]
があります。 2つの STORE_FAST
opcodes は、これらの2つの値を取り、割り当ての左側の名前に格納します。最初のSTORE_FAST
はスタックの最上部の値をポップし、それをa
に入れ、次は再びポップして、値をb
に格納します。 Pythonは、左側のターゲットリストの割り当てが左から右に行われることを保証するため、ローテーションが必要です。
3つの名前の割り当ての場合、 ROT_THREE
に続いてROT_TWO
が実行され、スタックの上位3項目が逆になります。
より長い左側の割り当てでは、明示的なタプルが作成されます。
>>> 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
オペコードを使用します。2つおよび3つの名前の割り当て操作でもそうします ただし、後の(のぞき穴)最適化ステップ =効率を上げるために、2つの引数を持つBUILD_Tuple
/UNPACK_SEQUENCE
の組み合わせを上記のROT_TWO
およびROT_THREE
オペコードに置き換えます。