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
2 つのLOAD_FAST
オペコード(変数からスタックに値をプッシュする) の後、スタックの一番上に[a, b]
. ROT_TWO
オペコードは、スタックの上位 2 つの位置を入れ替えて、スタックが[b, a]
最上位になるようにします。次に、2 つのSTORE_FAST
オペコードはこれら 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 つの名前の代入操作でもそうしますが、後の (のぞき穴) 最適化ステップBUILD_TUPLE
では、 /のUNPACK_SEQUENCE
組み合わせと 2 つまたは 3 つの引数を上記ROT_TWO
およびROT_THREE
オペコードに置き換えて効率を高めます。