記事読んだ:"dis/inspectモジュールを使ったPythonのハッキング"

dis/inspect モジュールを使った Python のハッキング

pythonコンパイラ,そしてバイトコードに関する素敵な記事です.以下なぐりがきメモ.

dis.dis(関数名)

import dis; dis.dis(関数名)で関数のバイトコードを見れる.コンパイラによってつくられたこのバイトコードはceval.cで実行される.pycがこの形式.

関数オブジェクト

関数オブジェクトのfunc_code属性がcodeオブジェクトで,これはco_varnames, co_names, co_constsなどの変数文字列を保持している.(ただしコンパイル時に名前と対応させるだけ)実行時にはローカル変数などはそれらの数字によってアクセスされる.

ローカル変数はLOAD_FAST/STORE_FASTが使われる.ローカル変数はコンパイル時に決まる.インデックスでアクセスできる.グローバル変数はLOAD_GLOBAL/STORE_GLOBALが使われる.グローバル変数は実行時に動的に決まるので辞書をつかう.というわけで文字列の比較を伴うグローバル変数に比べてローカル変数のほうが早くアクセスできる仕組み.

FrameObject

普通のCPUのかわりに,PythonVMはmodule, class, functionそれぞれにFrameObjectをわりあてて実行する.PyFrameObject構造体.この中に最後に実行した命令を指すf_lastiや,ソース行との対応であるf_lineno,そしてデータの位置やサイズなどの情報が含まれている.

Code Object/ FrameObject / Function Object

ModuleとClassはFrameObjectとCodeObjecを持つ.FunctionはFrameObject, FunctionObject, CodeObjectの3つを持つ.

  • f_ではじまるのはFrameObject実行時にダイナミックに生成され変化するフレームオブジェクト.frame.
  • func_ではじまるのはFuncionObet実行時に定まる関数オブジェクト.function.
  • co_ではじまるのはCodeObjectコンパイル時に定まるコードオブジェクト.code

MAKE_FUNCTION

実行時に関数を作る.ただCodeObjectはコンパイル時にできている.デフォルト引数への参照がデータスタックに詰まれ,MAKE_FUNCTIONはこれを使う.CALL_FUNCTIONは引数の個数をオペランドにとる.というわけでこの二つをつかえば関数を呼ぶときにデフォルトの引数とそうでない引数の数がわかる.

import inspect as ins; ins.stack()[0][0].f_code.co_code
とかすると命令のhexの値などがみれる.自分用のinspectモジュールを使うとスコープ内の変数が影響されなくてすむ.

Python Object

PyObject_HEADはリファレンスカウントとオブジェクトのtypeをつけくわえるマクロ.これが付いている構造体ならばPyObject型にキャストしてそのリファレンスカウントを得ることができる.PyDictObjectとか.