Published On: 1970-01-01|Last Updated: 1970-01-01|Categories: Uncategorized|
Python で例外が発生したら、状態を回復して、例外が起こった場所から実行をやり直すという馬鹿げた話。

クロージャは考慮してない。

例外が起こったスコープの frame オブジェクトを取得する

こんな感じ ?

import inspect
frame = inspect.trace()[1][0]

状態 (frame.f_globals, frame.f_locals, frame.f_lasti) を取得する。

__dict__ = dict(frame.f_globals)
if '__builtins__' not in __dict__:
__dict__['__builtins__'] = sys.modules['__builtin__']
__dict__.update(frame.f_locals) #理由は後述

例外に応じた適切な回復処理を行う。

いわゆるユーザ定義コード

例外発生箇所 (の次のコード) から実行できるようにコードに手を加える。

Python インタープリタはコードを実行する前段階の処理として、frame.f_code.co_varnames に存在する名前を locals から削除する。このため、状態を再現するために locals を exec や eval から渡すことは不可能である。これを回避するために、locals を globals に上書きし、初期化コードとして frame.f_code.co_varnames 内の名前を LOAD_GLOBAL -> STORE_FAST でロードする。

JUMP_ABSOLUTE 再開位置 *1 を初期化コードに加える。

RETURN_VALUE されても返り値を取得できないので、適当に STORE_GLOBAL 0; LOAD_CONST 0 を RETURN_VALUE の前に挿入する。

いろいろインデックスがずれるので、絶対ジャンプ系命令と相対ジャンプ系命令を修正する。

修正した code オブジェクトを生成する。

import new
new.code(
0,
frame.f_code.co_nlocals,
frame.f_code.co_stacksize,
frame.f_code.co_flags,
修正したコード,
frame.f_code.co_consts,
frame.f_code.co_names + frame.f_code.co_varnames,
frame.f_code.co_varnames,
frame.f_code.co_filename,
frame.f_code.co_name,
frame.f_code.co_firstlineno,
frame.f_code.co_lnotab, #これを修正するのも面倒くさい
frame.f_code.co_freevars,
frame.f_code.co_cellvars,
)

実行する。

exec code in __dict__
# または
eval(code, __dict__)
# 返り値(のハズ)
__dict__[frame.f_code.co_names[0]]

動くことだけは確認したけど、人前に出せるほどのサンプルコードはできてない。

というかコードオブジェクトを関数に加工すれば locals の引き渡しも返り値の取得も簡単かもしれないと今気付いた。

*1:frame.f_lasti が例外発生位置だが、再開位置の判別は研究中。というか簡単なインタープリタ書かないと正確な判別はできないんだろうね

関連