Pythonデコレータが熱い
今回は
decorator 3.4.0 : Python Package Index
のお話。
Pythonのデコレータは便利です。関数の実体定義直後に、任意のフィルタをその関数に施すことができます。
def special(func): def invoker(*args, **kw): return "special " + func(*args, **kw) invoker.__name__ = func.__name__ invoker.__dict__.update(func.__dict__) invoker.__doc__ = func.__doc__ invoker.__module__ = func.__module__ return invoker #改変した関数を返す @special def myproc(): return "hoge" #ここで myproc = special(myproc) とするのと同じ print myproc() >> "special hoge"
うん、悪くない。けど、現実には、クロージャを意識してネストした関数を書くような実装を、ビジネスロジックを書く人(というか、書いているときの自分)に強制したくない。となると、デコレータをパラメータ化し、いちいち再実装しなくていいようにしたい。で、ありがちなのはこんなコード。
#パラメータ化されたデコレータの実装 def wrap_with(before_start, after_finish): def wrap_decorator(func): def invoker(*args, **kw): before_start(*args, **kw) r = func(*args, **kw) after_finish(*args, **kw) return r invoker.__name__ = func.__name__ invoker.__dict__.update(func.__dict__) invoker.__doc__ = func.__doc__ invoker.__module__ = func.__module__ return invoker return wrap_decorator #ここからユーザのコード def before_start(*args, **kw): print "start" def after_finish(*args, **kw): print "finish" @wrap_with(before_start, after_finish) def target_func(): print "processing..." target_func() >> start >> processing... >> finish
3重の関数定義ネスト! 上手く動いてるうちはいいけど、バグ調査がデコレータの内部実装に及んだとき、これはきつい。コードレビューが激しく遅くなります。関数プログラムだから難しいんじゃ?とお思いの方、__call__メソッドをオーバーライドし、OOPで書いてみてもいいですよ。余計めんどくさくて、私は断念しました。
じっさい、TurboGearsのコントローラにくっつける例のやつには、こんなのがいっぱいあって、とてもじゃないけどデコレータの中まで追いかけきれません。
というわけで、decoratorモジュールです。
http://pypi.python.org/pypi/decorator/
http://www.phyast.pitt.edu/~micheles/python/documentation.html
これは、デコレータの実装をもっと簡潔にしようというもの。これで最初の例は、
from decorator import decorator def special(func, *args, **kw): return "special " + func(*args, **kw) @decorator(special) def myproc(): return "hoge" print myproc() >> "special hoge"
もしくは
from decorator import decorator @decorator def special(func, *args, **kw): return "special " + func(*args, **kw) @special def myproc(): return "hoge" print myproc() >> "special hoge"
となります。前者は任意の関数をその場でデコレータ化して適用、後者はデコレータをあらかじめ作成しておく方法です。
パラメータ化デコレータの場合はこれ。
from decorator import decorator def wrap_with(before_start, after_finish): def invoker(func, *args, **kw): before_start(*args, **kw) r = func(*args, **kw) after_finish(*args, **kw) return r return decorator(invoker)
ネストが2重で済みました。
「OOPは解るけど、関数プログラムだとクロージャに慣れてなくて自信ないんだよね」という方には、こんな方法もありました。
class WrapDecorator(object): def __init__(self, before_start, after_finish): self.before_start = before_start self.after_finish = after_finish def call(self, func, *args, **kw): self.before_start(*args, **kw) r = func(*args, **kw) self.after_finish(*args, **kw) return r wrap_with = decorator(WrapDecorator)
できあがったデコレータの使い方はまったく同じです。
def before_start(*args, **kw): print "start" def after_finish(*args, **kw): print "finish" @wrap_with(before_start, after_finish) def target_func(): print "processing..." target_func() >> start >> processing... >> finish
http://www.phyast.pitt.edu/~micheles/python/documentation.html
には、もっと面白い実用方法が掲載されていて、ためになるのでぜひ一度見てみることをお勧めします。メモ化(時間のかかる処理は一度出した答えを憶えておく)やJavaのsynchronizedメソッドをデコレータで実現する例では、こんなにコードが簡潔なまま、現実的な応用が可能なのかと感心することでしょう。