浅談(x,y)=(y,x)

4050 ワード

2つの変数の値を交換します.最も一般的な書き方は次のとおりです.
>>> temp = x
>>> x = y
>>> y = temp

しかし、実はもっとPythonicの書き方はこうです.
>>> x, y = y, x

なぜPythonでこのように2つの変数の値を交換できるのか考えたことがありますか?
Pythonコードは、先に解釈(ここでの解釈はコンパイルに対して、PythonはC/C++のようなコンパイル言語とは異なり、ソースファイルからマシン命令にコンパイルする必要がある)からPythonバイトコード(byte code,.pycファイルは主にこれらのバイトコードを格納するために使用される)になった後、Python解釈器によってこれらのバイトコードを実行する.一般的に、Python文はいくつかのバイトコード命令に対応し、Pythonのバイトコードはアセンブリ命令の中間言語に似ているが、1つのバイトコードは1つのマシン指定に対応するだけではない.
内蔵モジュールdisは、バイトコードを解析するために使用することができる.DOC
The dis module supports the analysis of CPython bytecode by disassembling it. The CPython bytecode which this module takes as an input is defined in the file Include/opcode.h and used by the compiler and the interpreter.
常用disモジュール方法:dis.dis([bytesource])
dis.dis([bytesource])Disassemble the bytesource object. bytesource can denote either a module, a class, a method, a function, or a code object. For a module, it disassembles all functions. For a class, it disassembles all methods. For a single code sequence, it prints one line per bytecode instruction. If no object is provided, it disassembles the last traceback.
dis.dis受信パラメータは、モジュール、クラス、方法、関数、またはオブジェクトであってもよいコードブロックであり、このコードブロックに対応するバイトコード命令シーケンスを得ることができる.
>>> import dis
>>> def test():
...     a = 1
...     
... 
>>> dis.dis(test)
  3           0 LOAD_CONST               1 (1)
              3 STORE_FAST               0 (a)
              6 LOAD_CONST               0 (None)
              9 RETURN_VALUE        

出力のフォーマットは、行番号、アドレス、命令、操作パラメータ、パラメータ解釈(変数名、定数値などを識別)です.
本題に入り、2番目の書き方のバイトコード命令を直接見てみましょう.
swap_2.py
x = 1
y = 3
x, y = y, x

python -m dis swap_2.py
  1           0 LOAD_CONST               0 (1)
              3 STORE_NAME               0 (x)

  2           6 LOAD_CONST               1 (3)
              9 STORE_NAME               1 (y)

  3          12 LOAD_NAME                1 (y)
             15 LOAD_NAME                0 (x)
             18 ROT_TWO
             19 STORE_NAME               0 (x)
             22 STORE_NAME               1 (y)
             25 LOAD_CONST               2 (None)
             28 RETURN_VALUE

一部のバイトコード命令は以下の通りです.具体的な命令は公式サイトに移動してください.
LOAD_CONST(consti)Pushes co_consts[consti] onto the stack.STORE_NAME(namei)Implements name = TOS. namei is the index of name in the attribute co_names of the code object. The compiler tries to use STORE_FAST or STORE_GLOBAL if possible.LOAD_NAME(namei)Pushes the value associated with co_names[namei] onto the stack.ROT_TWO()Swaps the two top-most stack items.
上記のバイトコード命令を説明します.
1行目はLOAD_CONSTSTORE_NAMEの2バイトコード命令を実行し、実行する動作はco_consts[0]スタック(すなわち定数テーブルの最初の定数、整数1がスタックに押し込まれた)を押してco_を取得するnames[0]の変数名x(変数名テーブルの最初の名前)、スタックトップ要素(整数1)スタックとco_names[0]はf->f_に格納されるlocals.
2行目の実行方法は1行目と同じです.
co_consts[0] = 1
co_names[0] = x
f->f_locals['x'] = 1

co_consts[1] = 3
co_names[1] = y
f->f_locals['y'] = 3

ポイント3行目、前の2行の計算順序はいずれも友から左へ(一般的にPython式の計算順序は左から右ですが、式の付与時には式の右のオペランドが左より優先されます)、つまり4行目はこのように実行され、まずメタグループ(y,x)を作成し、実行する動作は2つのLOAD_NAMEで、local,global,builtin名前空間のco_を順に検索しますnames[1](対応変数名y)とco_names[0](対応変数名x)は、対応する値をスタックに圧縮する.次に実行される動作は、交換ROT_TWOであり、交換スタックの上部の2つの要素位置である.
次の実行命令から分かるように、まずco_を取得します.names[0]の変数名xは,スタックトップ要素(現在は元のyの値)がスタックから出て格納され,2回の格納で2つの変数の値を交換することを実現する.
第2の方法は、任意の中間変数を用いず、より良い性能を得ることができる.簡単にテストできます.
>>> from timeit import Timer
>>> Timer('temp = x;x = y;y = temp', 'x=2;y=3').timeit()
0.030814170837402344
>>> Timer('x, y = y, x', 'x=2;y=3').timeit()
0.027340173721313477

なぜ2つ目の方法で消費する時間が少ないのでしょうか.中間変数の付与による時間の消費であると推測できる.具体的な検証は、次の2つの方法のバイトコード命令を解析することができる.
Life such short,be Pythonic .
Blog : JunNplus