decoratorの挙動をちょっと変更しつつ適用する
メモ。
motivatation
最近、fanstaticを使っている時にデコレータを利用したいと思った。
(fanstaticはWSGIアプリのResponseの中を覗いてhead要素の合間に定義しておいたresource(js,cssなど)を読み込むタグを追加してくれるライブラリ((他にも機能はある)))
たいていの場合はjqueryとbackbone.jsとunderscore.jsを利用。そして、時々、ある関数だけjquery.toolsを使いたいとおもったりなどした。
このような時に以下のような機能が欲しいと思った。
- 主として利用する機能はデフォルトで提供して欲しい
- 時々、上で定義したデフォルトに+αで挙動を変更したい(具体的には、サポートするresourceを追加したい)
1つ目は、デコレータファクトリーのようなもの作れば解決できるだろうと思った。
2つ目は、作ったデコレータ同士を合成したり、機能を追加できれば良いだろうと思った。
code
こんな感じ。
特にfanstaticにこだわることはないので、今回はActionDecoratorFactoryという名前にした。
このクラスが生成するデコレータは、デコレート対象の関数の戻り値が返えされる前に、定義しておいたアクションを呼べる。
class ActionDecoratorFactory(object): def __init__(self, fns=None): self.fns = fns or set() self._input = None self._output = None def add(self, fn): new = ActionDecoratorFactory(self.fns[:]) new.fns.append(fn) return new def merge(self, other): new = ActionDecoratorFactory(self.fns[:]) new.fns.extend(other.fns) return new def __call__(self, wrapped): def with_action(*args, **kwargs): self._input = (args,kwargs) self._output = wrapped(*args, **kwargs) self.do_action() return self._output return with_action def do_action(self): for f in self.fns: f(self)
ひとつだけ注意点がある。(あとで書く)
how to use
このように使う。テキトーな関数を用意。
作ったクラスに引数として渡す。
def print_foo(self): print "foo" def print_boo(self): print "boo" def print_bar(self): print "bar" with_foo = ActionDecoratorFactory([print_foo])
通常の利用時には生成されたデコレータをそのまま使う。
@with_foo def identity(x): return x
何か機能を追加したい時には
何か機能を追加したい時には、self.add(decorator)という形で呼ぶ。(chainはできないので注意)
@with_foo.add(print_boo) def sq(x): return x * x sq(10) # foo # boo
複数のデコレータを合成したい場合には、
複数のデコレータを合成したい場合には、self.merge(decoratorfactory)という形で呼ぶ。
boo_decorator = ActionDecoratorFactory([print_boo]) @boo_decorator.merge(ActionDecoratorFactory([print_foo, print_bar])) def double(x): return 2*x double(2) # boo # foo # bar
注意点
self.addとself.mergeが新しいインスタンスを返しているところ。
こうしないと、add,mergeに使った副作用(機能の追加/合成)が、他のデコレータ対象にも伝搬してしまう。