Python: replaceFuncをモジュールにして、複数関数置換にも対応させる

ラベル: , ,
 関数内の関数を置換する関数replaceFunc()をモジュールにして、さらにキーワード引数で複数の関数置換にも対応しました。

前の関連記事:Python: 関数内の関数を他の関数から置換する: replaceFunc


compile()に渡すコードにimportは使わないことにする


ReplaceFunc/RepalceFunc/src at a593055995bab1758e03c130ecabd9f88cc04681 · p--q/ReplaceFunc

Python: 関数内の関数を他の関数から置換する: replaceFuncでcompile()に渡すコードでimportする方法は問題が起こることと、同じモジュールを2回コンパイルするという無駄なことをしていることがわかったので、importはやめて渡したい関数を文字列にして挿入するようにしました。
from replacefunc import replaceFunc
def newFunc(arg):  # 置換後の新しい関数。
    print("置換後の関数で{}を出力。".format(arg)) 
@replaceFunc(print, newFunc)  # 引数付きデコレータ。グローバルスコープで使うと2回実行されてしまう。
def targetFunc():  # この関数の中の関数を置換する。
    print("ターゲットの関数")
targetFunc()
これを実行するとtargetFunc()内のpirnt()がnewFunc()に置換されて実行されます。

replaceFuncで複数の関数を置換できるようにする


p--q/ReplaceFunc

ここからreplacefunc.pyをダウンロードしてPYTHONPATHに置きます。
from replacefunc import replaceFunc
def newFunc(arg):  # 置換後の新しい関数。
    print("置換後の関数で{}を出力。".format(arg)) 
    
def newFunc2(arg):
    print("置換後の関数2で{}を出力。".format(arg))
    
@replaceFunc(print=newFunc, len=newFunc2)  # 引数付きデコレータ。
def targetFunc():  # この関数の中の関数を置換する。
    print("ターゲットの関数")
    len("ターゲットの関数2")
    
targetFunc()
targetFunc.__wrapped__()
tagetFunc()で置換後の関数が呼び出されます。

targetFunc.__wrapped__()で置換していないオリジナルの関数が呼び出されます。

キーワード引数で、置換前の関数=置換後の関数オブジェクト、を指定します。
def targetFunc():  # この関数の中の関数を置換する。
    #replace functions
    len = newFunc2
    print = newFunc
    print("ターゲットの関数")
    len("ターゲットの関数2")
replaceFunc()によってtargetFunc()はこのように変更されてから、実行されます。

exec()にオブジェクトを渡す方法がようやくわかる


Python Cookbook09.23.executing_code_with_local_side_effectsにちゃんと解説がありました。

このtest4()をみるとexec(コード, グローバル名前空間の辞書, ローカル名前空間の辞書)の名前空間の辞書に予め入れておいた値をコードの中で使用できています。

これまでexec()実行後の名前空間の辞書の中身をみても実行前に渡したはずの値が入っていなかったので、そんなことはできないと思っていましたが、この名前空間はexec()の実行によって初期化されるので、実行前の値は入っていない、ということでした。

 exec(compile(src,'generated in replaceFunc','exec'), glb, loc)

generated in replaceFuncというのが仮想モジュール名でglbがグローバルの名前空間の辞書、locがローカルの名前空間の辞書になっています。

srcにある関数の中から使いたい関数があるので、glbにその関数おモブジェクトを入れるとsrcの中の関数でつかけるようになりました。

glbもlocもexec()によって初期化されるので、exec()実行後にglbを見てみると、exec()実行前の値は入っていたり入っていなかったりします。

exec()で実行した関数を取り出すときはglbでは取り出せず何故かlocから取り出せます。

どうも納得できないですが、とりあえず、exec()で実行するコードに関数オブジェクトを渡すことができたので、一旦ソースに戻してまた実行するという無駄なことをしていたことが省略できました。

Python Cookbookにはなるべくexec()を使わない方法を考えるように繰り返し書いてありますが、そのような方法は思いつきませんでした。

文字列を渡すと関数内のコードを置換するようにする


ReplaceFunc/replacefunc.py at 46ca75e464b13b5a52a79c9cb1ea77f4e28a65e6 · p--q/ReplaceFunc

キーワード引数の値が文字列のときは単純にコードの文字列置換をするようにしました。

文字列置換後のコードの妥当性は評価しないので慎重に文字列を選ばないといけません。

動的に編集したコードを確認できるようにする


targetFunc = replaceFunc(print=newFunc, len=newFunc2)(targetFunc, debug=True)
デコレートされる関数と一緒にキーワード引数(キーワードは問わない)を渡すと、編集後のコードをprint()します。

p--q/ReplaceFunc

これで完成したのですが、動的に編集したコードにはブレークポイントを設定できないことに気が付きました。

デバッグするときだけ関数を置換する目的でreplaceFuncを作成したのに、これではお蔵入りになりそうです。

次の関連記事:Python: replaceFuncをデバッガに対応させる

PR

0 件のコメント:

コメントを投稿