Python入門:デコレータとは
2009.11.11
前から常々思っていることだが、何かについて勉強する一番効率的な方法はそれを誰かに教えること。人に教えようとすると、それなりに準備をしなければならないし、自分の頭の中を整理しなければならない。また教える過程でするどい質問をされたり間違いを指摘されて、さらに勉強を強いられることもある。
私がこの手の「入門編エントリー」を書くのは、ほとんどの場合「自分自身の理解をより深めたい」ことが一番の目的であるが、ブログの場合、教室などと違って「その道の達人」みたいな人たちがツッコミを入れてくれるケースもしばしばあるので、そのメリットは何倍にもなる。
先日のクロージャに関するエントリーなどは良い例で、「そんな用途にはmemoizeというデコレータが便利」などの指摘がいただけだけであれを書いた価値があるというもの。
そこで、今日はPythonのデコレータに関して。デコレータがPythonという言語に導入された経緯に関してはここに詳しい経緯が書いてあるが、ひと言で言えば、それまでのPythonでクラスメソッドを定義するには、
def foo(self):
メソッドの本体
...
foo = classmethod(foo) #ここで実はクラスメソッドだと宣言
としなければならなかったのだが、これでいかにも後付けで言語として格好が悪い(手紙の冒頭に「あなたは美しい、あなたが好きだ、あなた無しでは生きて行けない」とさんざん書いておいて、最後に「これはあなた向けに書いたものではなく、単なる『詩』です」と追記するようなもの)、ということで導入されたのが以下のデコレータである(最初に『詩』だと宣言しておけば、ラブレターと誤解されることもない)。
@classmethod
def foo(cls):
メソッドの本体
...
興味深いことに、Pythonの言語を定義している人たちは、この@classmethodというデコレータを特別扱いせず、一般化してだれにでもデコレータを作れるように言語を拡張した。そのおかげで、それ以来色々なデコレータが作られ、さまざまなところで活用されている。先に出て来たmemoizeもその一つである。
簡単な例として、「俳句を返す関数」を「短歌を返す関数」に変更してしまう haiku_to_tanka デコレータを実装してみよう。例えば、
def furuike():
return u"古池や蛙飛びこむ水の音"
という俳句を返す関数があった場合、それが短歌を返すようにデコレーションするには、
def haiku_to_tanka(original_func):
def decorated_func(*args):
return original_func(*args) + u" それにつけても金の欲しさよ"
return decorated_func
というデコレータを前もって定義しておき、
@haiku_to_tanka
def furuike():
return u"古池や蛙飛びこむ水の音"
としてあげるだけでよい。先のclassmethodデコレータを同じく、これは実質的には
def furuike():
return u"古池や蛙飛びこむ水の音"
furuike = haiku_to_tanka(furuike)
と等価である。
なのでその後に関数furuike()を呼ぶと、オリジナルのfuruike()ではなく、デコレータによって再定義された関数(decorated_func)が呼ばれ、それがオリジナルのfuruike()が返した俳句に下の句を追加した上で短歌として返すことになるのだ。
haiku_to_tanka関数内部で定義されているdecirated_funcはhaiku_to_tankaに渡されたoriginal_funcを参照している点に注目して欲しい。これにより、haiku_to_tankaに渡された(つまりデコレートされた)関数(オリジナルのfuruike)が再定義される関数の中に閉じ込められる形(つまりクロージャ)になっているのだ。
ちなみに、先日教わったばかりのmemoizeというデコレータ。すでに何通りかの実装がされているのだが(例1、例2)、どれもここで見せるサンプルとしてはいまいち分かりにくいので、クロージャを使って出来る限りシンプルに実装してみたのがこれ。
def memoize(original_func):
cache = {}
def decorated_func(*args):
try:
return cache[args]
except KeyError:
cache[args] = original_func(*args)
return cache[args]
return decorated_func
このデコレータは、先の kaiku_to_tanka デコレータと同じく、オリジナルの関数 original_func をクロージャの形で閉じ込めた新たな関数を作り出す。同じクロージャ内には、関数へのパラーメータをキーとしたcacheというディクショナリを用意しておき、キャッシュ済みであればそれを返すし、そうでなければ(except KeyError: でキャッチしている)オリジナルの関数を呼び出した上でその値をキャッシュしてから返す。
memorizeにどうしても見えてしまうのは分かる(自分も最初そうだった)のですが実は「r」がなくて「memoize」です。
Posted by: Andy | 2009.11.11 at 08:54
ご存知かもしれませんが以下のnewsがgoogleから
◇「Pythonのように高速で、CやC++のように安全」
Google、オープンソースのプログラミング言語「Go」リリース
http://headlines.yahoo.co.jp/hl?a=20091111-00000028-zdn_ep-sci
Posted by: dotcom | 2009.11.11 at 16:36
memoize目的だけであれば、わざわざ例外を発生させる必要は無いですね。
try...except 部分は下記のように1行でOK.
return cache.setdefault(args, org_func(*args))
Posted by: こむそう | 2009.11.20 at 21:00