■
あと
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さんにやってみたらと言われてやってみたが思ったより解読は楽だな。