概要
disモジュールは、バイトコードの解析をサポートしてくれるモジュールです。
今、書いた処理が実際には、どんな感じに解釈されるのかなーって、稀に気になったときに使えます。
結構見ていると面白いです。
dis.dis()
よく参考書とかに載っているのは、dis.dis()
ですね。
文字列で渡してるサンプルをよく見ますね。実際は文字列でもオブジェクトでもオッケーです。
dis.dis('[x for x in range(1000) if x % 2 == 0]')
以下のように出力されます。(dis.dis()
は file引数を指定しない場合、デフォルトで標準出力に出ます。)
1 0 LOAD_CONST 0 (<code object <listcomp> at 0x00000000094DBAE0, file "<dis>", line 1>) 2 LOAD_CONST 1 ('<listcomp>') 4 MAKE_FUNCTION 0 6 LOAD_NAME 0 (range) 8 LOAD_CONST 2 (10000) 10 CALL_FUNCTION 1 12 GET_ITER 14 CALL_FUNCTION 1 16 RETURN_VALUE
複数行でもいけます。
dis.dis(''' r = [] for x in range(10000): if x % 2 == 0: r.append(x) ''')
以下のように出力されます。
2 0 BUILD_LIST 0 2 STORE_NAME 0 (r) 3 4 SETUP_LOOP 38 (to 44) 6 LOAD_NAME 1 (range) 8 LOAD_CONST 0 (10000) 10 CALL_FUNCTION 1 12 GET_ITER >> 14 FOR_ITER 26 (to 42) 16 STORE_NAME 2 (x) 4 18 LOAD_NAME 2 (x) 20 LOAD_CONST 1 (2) 22 BINARY_MODULO 24 LOAD_CONST 2 (0) 26 COMPARE_OP 2 (==) 28 POP_JUMP_IF_FALSE 14 5 30 LOAD_NAME 0 (r) 32 LOAD_ATTR 3 (append) 34 LOAD_NAME 2 (x) 36 CALL_FUNCTION 1 38 POP_TOP 40 JUMP_ABSOLUTE 14 >> 42 POP_BLOCK >> 44 LOAD_CONST 3 (None) 46 RETURN_VALUE
みてみると、同じ事をしているのに、listcompとfor-loopで逆アセンブル結果が
異なってますね。よく聞く for-loop よりも listcomp の方がちょっとだけ速いってのは
for-loop側にだけある
32 LOAD_ATTR 3 (append)
の差でしょうか。
code オブジェクト と dis.Bytecode()
listcompの部分が
LOAD_CONST 0 (<code object <listcomp> at 0x00000000094DBAE0, file "<dis>", line 1>)
って形になっています。意味としてはそのまんまで code オブジェクトを表しています。
実は、dis.dis()
さんに文字列でコード断片を渡した場合は、内部で code オブジェクトに変換してから処理されています。
自前でcode オブジェクトを取得する場合、典型的なやり方は 組み込み関数の compile()
を使用します。
listcomp_code = compile('r = [x for x in range(1000000) if x % 2 == 0]', filename='<string>', mode='exec')
でも、python 3.4 からは、disモジュールに dis.Bytecode()
というものが追加されています。
こちらを使ったほうが何かと楽です。
listcomp_bytecode = dis.Bytecode('r = [x for x in range(1000000) if x % 2 == 0]')
Bytecodeオブジェクトは、内部に code オブジェクトを保持しています。
codeobj フィールドからアクセスできます。
listcomp_bytecode.codeobj <code object <module> at 0x0000000008BACC90, file "<disassembly>", line 1>
また、このオブジェクトの dis() を呼び出すと、dis.dis()
と同じ結果が得られます。
注意点として文字列の戻り値で返ってくるので、同じように出力するには print() が必要です。
print(listcomp_bytecode.dis())
さらに、info()
の呼び出しで、dis.code_info()
と同じ結果も得られます。
print(listcomp_bytecode.info())
以下のように出力されます。
Name: <module> Filename: <disassembly> Argument count: 0 Kw-only arguments: 0 Number of locals: 0 Stack size: 3 Flags: NOFREE Constants: 0: <code object <listcomp> at 0x0000000008BACC00, file "<disassembly>", line 1> 1: '<listcomp>' 2: 1000000 3: None Names: 0: range 1: r
dis.Bytecode()
使ったほうが楽ですね。
サンプル
# coding: utf-8 """ dis モジュールについてのサンプルです。 """ import dis from trypython.common.commoncls import SampleBase from trypython.common.commonfunc import hr # noinspection SpellCheckingInspection class Sample(SampleBase): def exec(self): ############################################## # dis モジュールは、pythonのバイトコードの # 解析をサポートしてくれるモジュール。 # # 大きく分けて2つの使い方がある # 1) dis.dis() # 2) dis.Bytecode() # # 1) は、指定された内容を逆アセンブルして出力してくれる。 # 引数の file に何も指定しない場合は標準出力に指定してくれる。 # # 2) は、python 3.4 で追加されたAPI。 # 指定の仕方は 1) とほぼ変わらないが、いきなり結果を # 出力ではなくて、一旦 Bytecode オブジェクトにラップして # 返してくれる。 # ############################################## listcomp_str = 'r = [x for x in range(1000000) if x % 2 == 0]' forloop_str = ''' r = [] for x in range(1000000): if x % 2 == 0: r.append(x) ''' ############################################### # dis.dis() ############################################### hr('dis.dis(listcomp_str)') dis.dis(listcomp_str) hr('dis.dis(forloop_str)') dis.dis(forloop_str) ############################################### # dis.Bytecode() # # python 3.4 から dis モジュールに追加されたAPI。 # 内部で code オブジェクトや dis.code_info() の # 結果を保持してくれたりするので、こちらの方が便利。 ############################################### hr('dis.Bytecode(listcomp_str)') listcomp_bytecode = dis.Bytecode(listcomp_str) print(listcomp_bytecode.codeobj) print(listcomp_bytecode.dis()) print(listcomp_bytecode.info()) hr('dis.Bytecode(forloop_str)') forloop_bytecode = dis.Bytecode(forloop_str) print(forloop_bytecode.codeobj) print(forloop_bytecode.dis()) print(forloop_bytecode.info()) def go(): obj = Sample() obj.exec() if __name__ == '__main__': go()
結果は以下のようになります。
----------------dis.dis(listcomp_str)---------------- 1 0 LOAD_CONST 0 (<code object <listcomp> at 0x00000000056B7ED0, file "<dis>", line 1>) 2 LOAD_CONST 1 ('<listcomp>') 4 MAKE_FUNCTION 0 6 LOAD_NAME 0 (range) 8 LOAD_CONST 2 (1000000) 10 CALL_FUNCTION 1 12 GET_ITER 14 CALL_FUNCTION 1 16 STORE_NAME 1 (r) 18 LOAD_CONST 3 (None) 20 RETURN_VALUE ----------------dis.dis(forloop_str)---------------- 3 0 BUILD_LIST 0 2 STORE_NAME 0 (r) 4 4 SETUP_LOOP 38 (to 44) 6 LOAD_NAME 1 (range) 8 LOAD_CONST 0 (1000000) 10 CALL_FUNCTION 1 12 GET_ITER >> 14 FOR_ITER 26 (to 42) 16 STORE_NAME 2 (x) 5 18 LOAD_NAME 2 (x) 20 LOAD_CONST 1 (2) 22 BINARY_MODULO 24 LOAD_CONST 2 (0) 26 COMPARE_OP 2 (==) 28 POP_JUMP_IF_FALSE 14 6 30 LOAD_NAME 0 (r) 32 LOAD_ATTR 3 (append) 34 LOAD_NAME 2 (x) 36 CALL_FUNCTION 1 38 POP_TOP 40 JUMP_ABSOLUTE 14 >> 42 POP_BLOCK >> 44 LOAD_CONST 3 (None) 46 RETURN_VALUE ----------------dis.Bytecode(listcomp_str)---------------- <code object <module> at 0x00000000056B7ED0, file "<disassembly>", line 1> 1 0 LOAD_CONST 0 (<code object <listcomp> at 0x00000000056B7F60, file "<disassembly>", line 1>) 2 LOAD_CONST 1 ('<listcomp>') 4 MAKE_FUNCTION 0 6 LOAD_NAME 0 (range) 8 LOAD_CONST 2 (1000000) 10 CALL_FUNCTION 1 12 GET_ITER 14 CALL_FUNCTION 1 16 STORE_NAME 1 (r) 18 LOAD_CONST 3 (None) 20 RETURN_VALUE Name: <module> Filename: <disassembly> Argument count: 0 Kw-only arguments: 0 Number of locals: 0 Stack size: 3 Flags: NOFREE Constants: 0: <code object <listcomp> at 0x00000000056B7F60, file "<disassembly>", line 1> 1: '<listcomp>' 2: 1000000 3: None Names: 0: range 1: r ----------------dis.Bytecode(forloop_str)---------------- <code object <module> at 0x00000000056C6030, file "<disassembly>", line 3> 3 0 BUILD_LIST 0 2 STORE_NAME 0 (r) 4 4 SETUP_LOOP 38 (to 44) 6 LOAD_NAME 1 (range) 8 LOAD_CONST 0 (1000000) 10 CALL_FUNCTION 1 12 GET_ITER >> 14 FOR_ITER 26 (to 42) 16 STORE_NAME 2 (x) 5 18 LOAD_NAME 2 (x) 20 LOAD_CONST 1 (2) 22 BINARY_MODULO 24 LOAD_CONST 2 (0) 26 COMPARE_OP 2 (==) 28 POP_JUMP_IF_FALSE 14 6 30 LOAD_NAME 0 (r) 32 LOAD_ATTR 3 (append) 34 LOAD_NAME 2 (x) 36 CALL_FUNCTION 1 38 POP_TOP 40 JUMP_ABSOLUTE 14 >> 42 POP_BLOCK >> 44 LOAD_CONST 3 (None) 46 RETURN_VALUE Name: <module> Filename: <disassembly> Argument count: 0 Kw-only arguments: 0 Number of locals: 0 Stack size: 3 Flags: NOFREE Constants: 0: 1000000 1: 2 2: 0 3: None Names: 0: r 1: range 2: x 3: append
参考情報
32.12. dis — Python バイトコードの逆アセンブラ — Python 3.6.3 ドキュメント
2. 組み込み関数 — Python 3.6.3 ドキュメント
29.12. inspect — 活動中のオブジェクトの情報を取得する — Python 3.6.3 ドキュメント
過去の記事については、以下のページからご参照下さい。
- いろいろ備忘録日記まとめ
サンプルコードは、以下の場所で公開しています。
- いろいろ備忘録日記サンプルソース置き場