2009年3月の段階で、果たしてデコレータを実際に使うのかどうか疑問なので、参考ページを載せるに留めておく。
「関数として呼び出し可能なオブジェクトを返す関数」で次々と元の関数を包み込んでいく感じ。クロージャとの合わせ技でもあるので、個人的には黒魔術の類であんまり使いたくない技法。まぁ、今の時点では。そのうち「デコレータマンセー!」になる可能性も0じゃない。
とにかく「デコレータ」として使える関数 or クラスを作るのがまずしんどい。単純に置き換えるだけなら以下のように、普通の関数でもデコレータとして使える。
>>> def d1(func): ... print "Hello, Decorator!" ... >>> @d1 ... def f1(arg): ... print "this line will be never executed." ... Hello, Decorator!
デコレートした時点でデコレータd1関数が実行されている。
>>> f1 >>> f1() Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'NoneType' object is not callable
そしてf1はd1()の戻り値、つまりNoneになってしまっている。これはつまり、
f1 = d1(f1)
と同じ事。
実用するならデコレータ関数は「呼び出し可能オブジェクト」相当を返す必要がある。クロージャでも良いし、__call__を実装したインスタンスでも良い。いずれにせよデコレータ関数が実行される場合は、第一引数にデコレート対象の呼び出し可能オブジェクトが渡される点に注意する。
多段デコレートも可能・・・なんだけど、面倒くさいなぁ。
t_decorator2.py :
class deco1(object):
def __init__(self, func):
self.__func = func
def __call__(self, *args, **kw):
print "entering to deco1"
self.__func(*args, **kw)
print "exit from deco1"
def deco2(func):
def wrapper(*args, **kw):
print "entering to deco2"
func(*args, **kw)
print "exit from deco2"
return wrapper
@deco1
@deco2
def f1(*args, **kw):
print repr(args)
print repr(kw)
f1("abc", "def", a=123, b=456)
>python t_decorator2.py
entering to deco1
entering to deco2
('abc', 'def')
{'a': 123, 'b': 456}
exit from deco2
exit from deco1
Pythonの場合はインスタンスの生成も関数呼び出しも同じ構文が使えてしまう為、上記のデコレートは下記のようになり、あっさり動いてしまう。
f1 = deco1(deco2(f1))
しかしこれ、デコレータに引数を渡すともっと相当ややこしいことになる。
@deco(args) def foo():pass
→
foo = deco(args)(foo)
→
_deco = deco(args) foo = _deco(foo)
呼び出し可能オブジェクトを2回も作らなくてはいけない!
t_decorator3.py :
class deco1(object):
def __init__(self, *args):
print "deco1 is initialized with args : ", repr(args)
self.__args = args
def __call__(self, func):
print "deco1 wraps func : ", repr(func)
def wrapper(*args, **kw):
print "entering to deco1(", repr(self.__args), ")"
func(*args, **kw)
print "exit from deco1"
return wrapper
def deco2(*dargs):
print "deco2 is initialized with args : ", repr(dargs)
def decorate(func):
print "deco2 wraps func : ", repr(func)
def wrapper(*args, **kw):
print "entering to deco2(", repr(dargs), ")"
func(*args, **kw)
print "exit from deco2"
return wrapper
return decorate
@deco1(1, 2, 3)
@deco2("ABC", "DEF")
def f1(*args, **kw):
print "in f1() : "
print repr(args)
print repr(kw)
f1("abc", "def", a=123, b=456)
> python t_decorator3.py
deco1 is initialized with args : (1, 2, 3)
deco2 is initialized with args : ('ABC', 'DEF')
deco2 wraps func : <function f1 at 0x00AF6EF0>
deco1 wraps func : <function wrapper at 0x00AF6F30>
entering to deco1( (1, 2, 3) )
entering to deco2( ('ABC', 'DEF') )
in f1() :
('abc', 'def')
{'a': 123, 'b': 456}
exit from deco2
exit from deco1
もう何がなにやら。しかもデコレータは引数の有無で、どのタイミングでデコレート対象のオブジェクトが渡るかが見事にずれて仕舞う為、兼ねさせる事ができない。一旦引数有りで作ったデコレータは、簡単には引数無しでデコレートさせることが出来なくなってしまう。
現実に使うにはこれだけじゃ機能が足りない。デコレート対象の関数の名前空間(__dic__)や__doc__, __name__属性の引き継ぎを行えないと困る。というわけで、functools.wraps()関数がEffectivePythonで紹介されている。
import functools
def deco(func):
print "decorating ..."
@functools.wraps(func)
def wrapper(*args, **kw):
print "entering to deco"
func(*args, **kw)
print "exit from deco"
print "decorated!"
return wrapper
@deco
def f1(*args, **kw):
"""
f1 document
"""
print "in f1() : "
print repr(args)
print repr(kw)
print "__doc__ = ", f1.__doc__
print "__name__ = ", f1.__name__
f1("abc", "def", a=123, b=456)
> python t_decorator4.py
decorating ...
decorated!
__doc__ =
f1 document
__name__ = f1
entering to deco
in f1() :
('abc', 'def')
{'a': 123, 'b': 456}
exit from deco
言語機能としては興味深いが、実際のアプリ開発のレベルで簡単に使えるか/使いたくなるかと言われれば・・・という感じ。
ライブラリを開発する場合は恐らく強力なツールになるだろうけど、テストをかなり注意深く行わないと、思いっきり嵌りそうな予感。