デコレータとは単なるシンタックスシュガーである。すなわち、
@deco
def foo():pass
は、
foo = deco(foo)
を意味する。例を見てみよう。
>>> def deco(func):
... return 'Hello, world!'
...
>>> @deco
... def foo():
... print "I'm never called."
...
>>> foo
'Hello, world!'
関数fooが関数decoの戻り値により'Hello, world!'という文字列に置き換わっている。
但し、通常デコレータの期待される用途としては、関数に機能を追加することである。
以下の例では関数fooに実行時間を表示する機能を追加している。
>>> import time
>>>
>>> def profile(func):
... def wrapper(*args, **kw):
... timer = time.clock
... t0 = timer()
... ret = func(*args, **kw)
... print '%s: %s sec elapsed' % (func.__name__, timer() - t0)
... return ret
... return wrapper
...
>>> @profile
... def foo(a, b):
... time.sleep(2)
...
>>> foo(1, 2)
foo: 1.99931766346 sec elapsed
ちなみに、wrapper関数内で参照しているfuncは、wrapper関数の外側の関数であるprofile関数の変数を参照している。
つまり、wrapper関数はクロージャであることに注意。
デコレータに引数を与えることも可能。
@deco(args)
def foo():pass
は、
foo = deco(args)(foo)
を意味する。分解すると、
_deco = deco(args)
foo = _deco(foo)
となる。
デコレータは入れ子にできる。
@A
@B
@C(args)
def f(): pass
は、
_deco = C(args)
f = A(B(_deco(f)))
を意味する。
デコレータは以下の2つのことに気をつける必要がある。
- 置き換えられる関数名を元の関数名にマッチさせておく
- 元の関数の属性を置き換えられる関数の属性にコピーしておく
以下の方法により実現できる。
>>> def deco(func):
... def wrapper(*args, **kw):
... return func(*args, **kw)
... wrapper.__name__ = func.__name__
... wrapper.__dict__ = func.__dict__
... wrapper.__doc__ = func.__doc__
... return wrapper
...
>>> @deco
... def foo():pass
...
>>> foo
<function foo at 0x00E5A6B0>
wapper関数の関数名が元の関数名のfooに置き換わっていることが確認できる。
この作業をライブラリ化したものが、functools.wraps()関数である。
デフォルトでは、'__module__'、'__name__'、'__doc__'を上書きして、
'__dict__'の内容を更新してくれる。
以下のように使用する。
>>> import functools
>>> def deco(func):
... @functools.wraps(func)
... def wrapper(*args, **kw):
... return func(*args, **kw)
... return wrapper
...
>>> @deco
... def foo():pass
...
>>> foo
<function foo at 0x00E5A6B0>
また、デコレータ関数自体をクラスで実現することも可能だが、
他のデコレータがクラスのインスタンスでなく関数を要求する場合に整合性が
崩れる恐れがあるので極力避けた方が良い。