Python のバイトコード(dis モジュール)で関数を解剖する

dis モジュールを使うと、Python 関数がどのようなバイトコードにコンパイルされるかを確認できます。パフォーマンス最適化やインタプリタの動作理解に有用です。

基本的な使い方

import dis

def add(a, b):
    return a + b

dis.dis(add)

出力例:

2           0 LOAD_FAST                0 (a)
              2 LOAD_FAST                1 (b)
              4 BINARY_ADD
              6 RETURN_VALUE

各行は「行番号、オフセット、命令、引数」を示します。

命令の意味

LOAD_FASTローカル変数をスタックにプッシュ
LOAD_CONST定数をスタックにプッシュ
BINARY_ADDスタックから 2 つポップして加算
RETURN_VALUEスタックトップを返す
CALL_FUNCTION関数呼び出し
STORE_FASTスタックトップをローカル変数に格納

ループの解析

def sum_list(items):
    total = 0
    for item in items:
        total += item
    return total

dis.dis(sum_list)

FOR_ITER や JUMP_BACKWARD などのジャンプ命令が現れます。

条件分岐の解析

def check(x):
    if x > 0:
        return "positive"
    else:
        return "non-positive"

dis.dis(check)

POP_JUMP_IF_FALSE など条件付きジャンプが使われます。

最適化の確認

def constant_folding():
    return 2 + 3

dis.dis(constant_folding)

定数畳み込みにより、コンパイル時に 5 に計算されていることがわかります。

Bytecode オブジェクト

より詳細な解析には Bytecode クラスを使います。

from dis import Bytecode

for instr in Bytecode(add):
    print(f"{instr.opname:20} {instr.argrepr}")

バイトコードの理解は、なぜ特定のコードが遅いのか、最適化がどう適用されるかを知る手がかりになります。