web-dev-qa-db-ja.com

タプル(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の値の交換は内部的にどのように機能しますか?一時変数は使用していません。

27
praveen

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オペコードに置き換えます。

70
Martijn Pieters