White scenery @showyou, hatena

If you have any comments, you may also send twitter @shsub or @showyou.

あと

foo(*xs)をやる関数を作ってimport disでバイトコードに変換して眺めるのがわかりやすい

とかいう話も来たのでdisとか知らないけどやってみる。


ってdisってdisassembleの略か・・


こんな感じかしら?

>>> def foo1():
...     hoge(xs)
...
>>> def foo2():
...     hoge(*xs)
...
>>> def foo3():
...     hoge(**xs)
...
>>> dis.dis(foo1)
  2           0 LOAD_GLOBAL              0 (hoge)
              3 LOAD_GLOBAL              1 (xs)
              6 CALL_FUNCTION            1
              9 POP_TOP
             10 LOAD_CONST               0 (None)
             13 RETURN_VALUE
>>> dis.dis(foo2)
  2           0 LOAD_GLOBAL              0 (hoge)
              3 LOAD_GLOBAL              1 (xs)
              6 CALL_FUNCTION_VAR        0
              9 POP_TOP
             10 LOAD_CONST               0 (None)
             13 RETURN_VALUE
>>> dis.dis(foo3)
  2           0 LOAD_GLOBAL              0 (hoge)
              3 LOAD_GLOBAL              1 (xs)
              6 CALL_FUNCTION_KW         0
              9 POP_TOP
             10 LOAD_CONST               0 (None)
             13 RETURN_VALUE

なんとなく6のとこで違いがあるってことはわかる。CALL_FUNCTIONとCALL_FUNCTION_VARとCALL_FUNCTION_KWって変わってるし。なんでその後の数値が1→0なのかはわからないけど。

ここを見ると説明が書いてある。

30.10 dis -- Pythonバイトコードの逆アセンブラ
http://www.python.jp/doc/release/lib/module-dis.html
30.10.1 Pythonバイトコード命令
http://www.python.jp/doc/release/lib/bytecodes.html

CALL_FUNCTION argc
関数を呼び出します。argcの低位バイトは位置パラメータを示し、高位バイトはキーワードパラメータの数を示します。オペコードは最初にキーワードパラメータをスタック上に見つけます。それぞれのキーワード引数に対して、その値はキーの上にあります。スタック上のキーワードパラメータの下に位置パラメータはあり、先頭に最も右のパラメータがあります。スタック上のパラメータの下には、呼び出す関数オブジェクトがあります。

CALL_FUNCTION_VAR argc
関数を呼び出します。argcはCALL_FUNCTIONのように解釈実行されます。スタックの先頭の要素は変数引数リストを含んでおり、その後にキーワードと位置引数が続きます。

CALL_FUNCTION_KW argc
関数を呼び出します。argcはCALL_FUNCTIONのように解釈実行されます。スタックの先頭の要素はキーワード引数辞書を含んでおり、その後に明示的なキーワードと位置引数が続きます。

CALL_FUNCTION_VAR_KW argc
関数を呼び出します。argcはCALL_FUNCTIONのように解釈実行されます。スタックの先頭の要素はキーワード引数辞書を含んでおり、その後に変数引数のタプルが続き、さらに明示的なキーワードと位置引数が続きます。

言ってることはよくわからないけどそれぞれ違う呼ばれ方をしてるっぽい。

追試

念の為2引数とか関数の中を2変数でdis.disにしてやんよしてみると、

>>> def foo11():
...     hoge(x,y)
...
>>> dis.dis(foo11)
  2           0 LOAD_GLOBAL              0 (hoge)
              3 LOAD_GLOBAL              1 (x)
              6 LOAD_GLOBAL              2 (y)
              9 CALL_FUNCTION            2
             12 POP_TOP
             13 LOAD_CONST               0 (None)
             16 RETURN_VALUE

>>> def foo12():
...     hoge(x)
...     hoge(y)
...
>>> dis.dis(foo12)
  2           0 LOAD_GLOBAL              0 (hoge)
              3 LOAD_GLOBAL              1 (x)
              6 CALL_FUNCTION            1
              9 POP_TOP

  3          10 LOAD_GLOBAL              0 (hoge)
             13 LOAD_GLOBAL              2 (y)
             16 CALL_FUNCTION            1
             19 POP_TOP
             20 LOAD_CONST               0 (None)
             23 RETURN_VALUE

>>> def foo13():
...     hoge(foo(x))
...
>>> dis.dis(foo13)
  2           0 LOAD_GLOBAL              0 (hoge)
              3 LOAD_GLOBAL              1 (foo)
              6 LOAD_GLOBAL              2 (x)
              9 CALL_FUNCTION            1
             12 CALL_FUNCTION            1
             15 POP_TOP
             16 LOAD_CONST               0 (None)
             19 RETURN_VALUE

って出る。


CALL_FUNCTIONの後の数値は引数の数っぽい(あとどうでもいいけど2 0の0って累計カラム数か)。LOAD_GLOBALでスタックに突っ込んで、CALL_FUNCTIONで実行、POP_TOPでTOPにあるのを取り除くという感じですか。


さらに、

>>> def foo14():
...     hoge(foo(x,y),z)
...
>>> dis.dis(foo14)
  2           0 LOAD_GLOBAL              0 (hoge)
              3 LOAD_GLOBAL              1 (foo)
              6 LOAD_GLOBAL              2 (x)
              9 LOAD_GLOBAL              3 (y)
             12 CALL_FUNCTION            2
             15 LOAD_GLOBAL              4 (z)
             18 CALL_FUNCTION            2
             21 POP_TOP
             22 LOAD_CONST               0 (None)
             25 RETURN_VALUE

>>> def foo15():
...     hoge(x,foo(y,z))
...
>>> dis.dis(foo15)
  2           0 LOAD_GLOBAL              0 (hoge)
              3 LOAD_GLOBAL              1 (x)
              6 LOAD_GLOBAL              2 (foo)
              9 LOAD_GLOBAL              3 (y)
             12 LOAD_GLOBAL              4 (z)
             15 CALL_FUNCTION            2
             18 CALL_FUNCTION            2
             21 POP_TOP
             22 LOAD_CONST               0 (None)
             25 RETURN_VALUE

とかやる。

どうもCALL_FUNCTIONをやると、n個分の引数をGLOBALスタックから巻き上げて関数を実行して、関数のあったとこに値を入れてるっぽい(foo15の6あたりがそう)。

あと関数でreturnをつけてやると、

>>> def foo16():
...     return hoge(x,foo(y,z))
>>> dis.dis(foo16)
  2           0 LOAD_GLOBAL              0 (hoge)
              3 LOAD_GLOBAL              1 (x)
              6 LOAD_GLOBAL              2 (foo)
              9 LOAD_GLOBAL              3 (y)
             12 LOAD_GLOBAL              4 (z)
             15 CALL_FUNCTION            2
             18 CALL_FUNCTION            2
             21 RETURN_VALUE

と出て来るので、returnを書かない関数の場合にのみPOP_TOPを実行してさらにNoneとか突っ込んで戻り値を返してるみたい。


nishioさんにやってみたらと言われてやってみたが思ったより解読は楽だな。

追記

で、このPythonアセンブリ命令がCPythonならC,JythonならJava実行環境上で解釈されるんですかね?