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 が例外発生位置だが、再開位置の判別は研究中。というか簡単なインタープリタ書かないと正確な判別はできないんだろうね