出てくる度に調べる割に、いつまで経っても覚えられなかったデコレータですが、今回こそ仕留める為にメモエントリーです。まずよく見かけるのは関数の時間を計測するという例ですね。僕はこの形だけを何となく覚えては忘れていました。
#!/usr/bin/python # -*- coding: utf-8 -*- import time # デコレータ定義 def time_func(func): def decorator(*args): start = time.time() ret = func() print '%s was executed, it took %s sec' % (func.func_name, time.time() - start) return ret return decorator # デコレータ適用 @time_func def test(): time.sleep(1) def main(): test() if __name__ == '__main__': main()
何となく関数を拡張する凄い機能みたいな認識だった訳ですが、今回調べてみるとデコレータは単なるシンタックスシュガーだという事が分かりました。上記のデコレータ適用の部分は以下と同じです。かなり見通しが良くなりましたね。
test = time_func(test)
またデコレータにパラメータを渡す事もできます。
#!/usr/bin/python # -*- coding: utf-8 -*- def dec_with_param(param1, param2): def decorator(func): def inner(*args): print 'decorated "%s" was executed, params: (%s, %s)' % (func.func_name, param1, param2) return func() return inner return decorator @dec_with_param(100, "param1") def test(): print "test" def main(): test() if __name__ == '__main__': main()
ネストが1階層増えて若干ややこしいですが、これもシンタックスシュガーだと考えると理解が進みます。一回パラメータを渡して渡して返ってきた関数にデコレータを適用しているだけですね。
test = dec_with_param(100, "param1")(test)
ここまでで大体デコレータの雰囲気は分かりました。そこで、ついでと言ってはなんですがdjangoのリクエストハンドラの結果をキャッシュする仕組みをデコレータで書きたいと以前から思っていたので書いてみました。
from google.appengine.api import memcache from ragendja.template import render_to_response # デコレータ定義 def cachenize(cache_time): def decorator(func): def inner(*args, **kwds): cache_key = unicode(kwds) res = memcache.get(cache_key) if not res: res = func(*args, **kwds) memcache.set(cache_key, res, cache_time) return res return inner return decorator # デコレータ適用 (この場合は結果を60secキャッシング) @cachenize(60) def some_request(request, param): # do something return render_to_response(request, 'path/to/template', {})
各リクエストハンドラはキャッシングを考えなくていいので非常にシンプルに実装できます。もちろん最適化はされていませんし、荒いですがGETリクエストパラメータと出力が1対1対応する場合のキャッシングは、これでも割と動くんじゃないかと思います。POSTパラメータが必要な場合もデコレータを少し書き換えるだけでいけますね。ウェブアプリケーションの場合だと各リクエストハンドラで似た様な処理を書く場合は多いのでデコレータが非常に役立ちそうです。
あと公式ドキュメントのこのページに色んなデコレータの例があって良かったです。

