1. ホーム
  2. python

[解決済み] dis.disの出力はどのように理解すればよいのでしょうか?

2023-03-26 19:13:52

質問

私は dis (Pythonのバイトコードのディスアセンブラ) . の出力をどのように解釈すればよいのでしょうか? dis.dis (または dis.disassemble )?

.

以下は非常に具体的な例です(Python 2.7.3の場合)。

dis.dis("heapq.nsmallest(d,3)")

      0 BUILD_SET             24933
      3 JUMP_IF_TRUE_OR_POP   11889
      6 JUMP_FORWARD          28019 (to 28028)
      9 STORE_GLOBAL          27756 (27756)
     12 LOAD_NAME             29811 (29811)
     15 STORE_SLICE+0  
     16 LOAD_CONST            13100 (13100)
     19 STORE_SLICE+1

なるほど JUMP_IF_TRUE_OR_POP などはバイトコード命令 (です(ただし、面白いことに BUILD_SET として動作すると思われますが、このリストには出てきません。 BUILD_TUPLE ) . 右側の数字がメモリ確保で、左側の数字が ゴー の数字だと思うのですが... 私は彼らが はほとんど は毎回 3 ずつ増加します (ただし、完全ではありません)。

もし私が dis.dis("heapq.nsmallest(d,3)") を関数の中に入れると

def f_heapq_nsmallest(d,n):
    return heapq.nsmallest(d,n)

dis.dis("f_heapq(d,3)")

      0 BUILD_TUPLE            26719
      3 LOAD_NAME              28769 (28769)
      6 JUMP_ABSOLUTE          25640
      9 <44>                                      # what is <44> ?  
     10 DELETE_SLICE+1 
     11 STORE_SLICE+1 

どのように解決するのですか?

ソースコードを含む文字列を逆アセンブルしようとしていますが、それは dis.dis ではサポートされていません。文字列の引数では、バイトコードを含むかのように文字列を扱います(関数 disassemble_stringdis.py ). つまり、ソース コードをバイト コードとして誤って解釈することに基づいて、無意味な出力が表示されているのです。

Python 3では事情が異なり dis.dis は文字列の引数をコンパイルします を逆アセンブルする前にコンパイルします。

Python 3.2.3 (default, Aug 13 2012, 22:28:10) 
>>> import dis
>>> dis.dis('heapq.nlargest(d,3)')
  1           0 LOAD_NAME                0 (heapq) 
              3 LOAD_ATTR                1 (nlargest) 
              6 LOAD_NAME                2 (d) 
              9 LOAD_CONST               0 (3) 
             12 CALL_FUNCTION            2 
             15 RETURN_VALUE         

Python 2 では、コードを自分でコンパイルしてから dis.dis :

Python 2.7.3 (default, Aug 13 2012, 18:25:43) 
>>> import dis
>>> dis.dis(compile('heapq.nlargest(d,3)', '<none>', 'eval'))
  1           0 LOAD_NAME                0 (heapq)
              3 LOAD_ATTR                1 (nlargest)
              6 LOAD_NAME                2 (d)
              9 LOAD_CONST               0 (3)
             12 CALL_FUNCTION            2
             15 RETURN_VALUE        

数字は何を意味するのか?数字は 1 はこのバイトコードがコンパイルされたソースコード内の行番号です。左の列の数字はバイトコード内の命令のオフセットで、右の列の数字は opargs . 実際のバイトコードを見てみましょう。

>>> co = compile('heapq.nlargest(d,3)', '<none>', 'eval')
>>> co.co_code.encode('hex')
'6500006a010065020064000083020053'

バイトコードのオフセット0には 65 のオペコードは LOAD_NAME というオペコードと 0000 である場合、(オフセット 3 で) 6a はオペコード LOAD_ATTR である。 0100 のようになります。引数はリトルエンディアンの順であることに注意してください。 0100 は数字1である。文書化されていない opcode モジュールにはテーブル opname は、各オペコードの名前を与えており opmap はそれぞれの名前に対応するオペコードを与えます。

>>> opcode.opname[0x65]
'LOAD_NAME'

opargの意味はopcodeに依存し、その全容はCPython仮想マシンの実装を読む必要があります ceval.c . の場合 LOAD_NAMELOAD_ATTR へのインデックスである。 co_names プロパティのインデックスです。

>>> co.co_names
('heapq', 'nlargest', 'd')

については LOAD_CONST へのインデックスです。 co_consts プロパティへのインデックスです。

>>> co.co_consts
(3,)

については CALL_FUNCTION では、関数に渡す引数の数で、下位バイトに通常の引数の数、上位バイトにキーワード引数の数で16ビットでエンコードされています。