デコレータ式を適用した関数から元の関数名を探す
初めまして。gumiの天野です。
今日は、pythonのデコレータについて自分が勉強した内容を書こうと思います。
動機
urlの解析にあたり、あるurlに対してDjangoがどのviewを呼び出すのかを調べることになりました。
Djangoではresolve()という関数がちょうどこの問題を解決してくれるのですが、実際に使用してみると困ったことが起こりました。
resolve()が返す関数にデコレータ式が適用されている場合、関数のfunc_nameにはデコレータが新しく返す関数の名前が入っています。
よって、単純にfunc_nameを読むだけでは元の関数名が正しく分からなかったのです。
そこで、デコレータでラップされた関数がどうやって元の関数にアクセスしているのかを調べる事になりました。
デコレータの動作
デコレータ式
@decorator1 def func1:pass
は以下の式と同じです。
def func1:pass func1 = decorator1(func1)
よって、デコレータの定義の方法は
1.関数を受け取り、新しい関数を返す関数
2.関数を受け取ってインスタンスを生成する、呼び出し可能なクラス
が考えられます。
1.関数でデコレータを定義する場合
新しく生成される関数は、func_closureという属性を通して元の関数を参照します。
func_closureはcellオブジェクトからなるタプルです。
よって、元の関数を指すcellオブジェクトをタプルの中から見つける必要があります。
2.クラスでデコレータを定義する場合
新しく生成されるのは、呼び出し可能なクラスのインスタンスです。
元の関数はインスタンス変数として参照されます。
例
def decorator1(f): def new_f(*args, **kwargs): print "decorated by decorator1" f(*args, **kwargs) return new_f @decorator1 def func1(a, b="hoge"): print "call func1(%s, b=%s)" % (a, b) class Decorator2(object): def __init__(self, f): self.f = f def __call__(self, *args, **kwargs): print "decorated by Decorator2" self.f(*args, **kwargs) @Decorator2 def func2(a, b="huga"): print "call func2(%s, b=%s)" % (a, b)
この場合、それぞれ
func1.func_closure[0].cell_contents
func2.f
がデコレータ適用前の関数になっています。