デコレータの引数

デコレータは引数を取ることができると Python 2.4 の What's new に書いてあった。
http://www.python.org/doc/2.4/whatsnew/node6.html

Decorator functions can take arguments. If arguments are supplied, your decorator function is called with only those arguments and must return a new decorator function; this function must take a single function and return a function, as previously described. In other words, @A @B @C(args) becomes:

  def f(): ...
  _deco = C(args)
  f = A(B(_deco(f)))

Getting this right can be slightly brain-bending, but it's not too difficult.

適当訳:

デコレータ関数は引数を取ることができる。もし引数が与えられると、あなたのデコレータ関数はそれらの引数とともに呼ばれ、そして新しいデコレータ関数を return しなければならない。前述の通り、この関数(どれ?)は1つの関数とり、1つの関数を返さなければならない。つまり、@A @B @C(args)*1はこうなる。

def f(): ...
_deco = C(args)
f = A(B(_deco(f)))

正しくこれを理解するのに頭をひねる*2かもしれないが、そんなに難しくない。

こんな変な機能何に使うんだ? と思ったら Python 2.5 から標準ライブラリ入りした functools で使われていた。
http://www.python.org/doc/2.5/whatsnew/pep-309.html

wraps() is a decorator that can be used inside your own decorators to copy the wrapped function's information. An alternate version of the previous example would be:

def my_decorator(f):
  @functools.wraps(f)
  def wrapper(*args, **kwds):
    print 'Calling decorated function'
  return f(*args, **kwds)
適当訳:

wraps()は、独自のデコレータの中で、ラップされる関数の情報をコピーするためのデコレータである。前の例は代わりにこう書ける。

def my_decorator(f):
  @functools.wraps(f)
  def wrapper(*args, **kwds):
    print 'Calling decorated function'
  return f(*args, **kwds)
なるほど。確かに引数を取れるようにするのは意味がある。

*1:改行が飛んでる?

*2:はてな記法で正しく引用する方がよっぽど頭をひねった。