Pythonのバイトコードとそのテキスト表現について

Pythonのバイトコードは、ソースコードがコンパイルされた後の中間表現であり、CPythonの仮想機械(PVM)によって実行されます。このバイトコードは通常.pycファイルとしてバイナリ形式で保存されますが、Python標準ライブラリのdisモジュールを用いることで、可読性の高いテキスト形式の命令群(アセンブラに似た mnemonics)に変換可能です。

以下に、関数のバイトコードを表示する基本的な使用例とその出力を示します。

import dis

def multiply(x, y):
    return x * y

dis.dis(multiply)

出力例(Python 3.11環境):

  2           0 LOAD_FAST                0 (x)
              2 LOAD_FAST                1 (y)
              4 BINARY_MULTIPLY
              6 RETURN_VALUE
列の位置 意味
2 対応するソースコードの行番号
0, 2, 4, 6 バイトコード命令の先頭オフセット(バイト単位)
LOAD_FAST, BINARY_MULTIPLY, RETURN_VALUE 実行される命令とその動作

より実践的な例として、文字列の連結と関数呼び出しを含む関数のバイトコードを確認します。

import dis

def format_message(user, greeting="Hi"):
    text = f"{greeting}, {user}!"
    print(text)

dis.dis(format_message)

出力(一部省略):

  2           0 LOAD_FAST                1 (greeting)
              2 LOAD_FAST                0 (user)
              4 BUILD_STRING           3
              6 STORE_FAST               2 (text)

  3           8 LOAD_GLOBAL            0 (print)
             10 LOAD_FAST                2 (text)
             12 CALL_FUNCTION          1
             14 POP_TOP
             16 LOAD_CONST             0 (None)
             18 RETURN_VALUE

主な命令の意味:

  • BUILD_STRING nn個の堆積された文字列オブジェクトを結合し、新しい文字列オブジェクトを生成
  • STORE_FAST:ローカル変数スタックの先頭値をローカル変数に保存
  • CALL_FUNCTION n:直前にロードされたオブジェクト(関数)を引数n個で呼び出し
  • POP_TOP:スタック上のトップ値を破棄(戻り値が不要な場合)
  • RETURN_VALUE:スタックトップの値を関数の戻り値として返却

※注: BINARY_ADDなど一部の命令は、Python 3.11以降ではBINARY_OP命令に統合され、操作種別が引数として与えられるよう変更されています。上記は比較的新しいバージョンに合わせた表現例です。

バイトコードは、CPythonの実装に依存しており、Pythonのバージョンごとに命令セットやフォーマットに違いがあります。そのため、異なるバージョン間でのバイナリ互換性は保証されません。また、.pycファイルは__pycache__ディレクトリ以下に保存され、ファイル名にはPythonのバージョン番号(例: cpython-311)が含まれます。

手動でコードオブジェクトを生成し、バイトコードを検査することも可能です。

import dis

src = "total = 10 + value"
code_obj = compile(src, "<dynamic>", "exec")
dis.dis(code_obj)

出力例:

  1           0 LOAD_CONST               0 (10)
              2 LOAD_NAME                0 (value)
              4 BINARY_OP                0 (+)
              6 STORE_NAME               1 (total)
              8 LOAD_CONST               1 (None)
             10 RETURN_VALUE

このように、disモジュールを用いることで、Pythonプログラムの内部動作を直接観察・分析することが可能であり、パフォーマンス最適化やデバッグ、言語実装の理解に非常に有用です。

タグ: CPython Bytecode dis compile abstract-machine

6月5日 20:52 投稿