Programmer's Gate / Effective Python /デコレータ

1   デコレータとは

デコレータとは単なるシンタックスシュガーである。すなわち、

@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関数はクロージャであることに注意。

2   デコレータに引数をとる

デコレータに引数を与えることも可能。

@deco(args)
def foo():pass

は、

foo = deco(args)(foo)

を意味する。分解すると、

_deco = deco(args)
foo = _deco(foo)

となる。

3   デコレータの入れ子

デコレータは入れ子にできる。

@A
@
B
@
C(args)
def f(): pass

は、

_deco = C(args)
f = A(B(_deco(f)))

を意味する。

4   デコレータの注意点

デコレータは以下の2つのことに気をつける必要がある。

  1. 置き換えられる関数名を元の関数名にマッチさせておく
  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>

また、デコレータ関数自体をクラスで実現することも可能だが、 他のデコレータがクラスのインスタンスでなく関数を要求する場合に整合性が 崩れる恐れがあるので極力避けた方が良い。


参考文献:

  1. Dr. Dobb's: Python 2.4 Decorators (http://www.ddj.com/web-development/184406073)
  2. pythonのデコレーター構文@decorate (http://www.nasuinfo.or.jp/FreeSpace/kenji/sf/python/virtualMachine/decorator.htm)