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オペコードに置き換えて効率を高めます。