いろいろ備忘録日記

主に .NET とか Go とか Flutter とか Python絡みのメモを公開しています。

Pythonメモ-38 (disモジュール) (バイトコードの逆アセンブラ, dis.dis, dis.Bytecode, dis.code_info, compile)

概要

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 ドキュメント

stackoverflow.com

stackoverflow.com

stackoverflow.com


過去の記事については、以下のページからご参照下さい。

サンプルコードは、以下の場所で公開しています。