クラスデコレータで__name__と__doc__を保持する


「初めてのPython」で紹介されてるデコレータで、適用されて関数が呼ばれた回数を出力する。

class Tracer(object):
    """関数の呼び出し回数を保存して出力するデコレータ"""

    def __init__(self, func):
        self.calls = 0
        self.func = func

    def __call__(self, *args):
        self.calls += 1
        print "call %s to %s" % (self.calls, self.func.__name__)
        self.func(*args)
@Tracer
def spam():
    """spam doc string"""
    pass

spam()
# => call 1 to spam
spam()
# => call 2 to spam


ただし__name__とか__doc__は保存されない

print spam.__doc__
# => 関数の呼び出し回数を保存して出力するデコレータ

print spam.__name__
# => AttributeError: 'Tracer' object has no attribute '__name__'


関数デコレータの場合はfunctools.wrapがこの辺の問題を解決してくれる
以下は「エキスパートPythonプログラミング」に載ってるサンプル

from functools import wraps

def mydecorator(func):
    @wraps(func)
    def _mydecorator(*args, **kwargs):
        """_mydecorator doc string"""
        print 'before call %s' % func.__name__
        res = func(*args, **kwargs)
        print 'after call %s' % func.__name__
        return res
    return _mydecorator


クラスを使ったデコレータの場合どうするかと思って調べてみたら、こんな方法が簡単そうだった。

class Tracer(object):
    """関数の呼び出し回数を保存して出力するデコレータ"""

    def __init__(self, func):
        self.calls = 0
        self.func = func

    def __call__(self, *args):
        self.calls += 1
        print "call %s to %s" % (self.calls, self.func.__name__)
        self.func(*args)

    __doc__ = property(lambda self: self.func.__doc__)
    __name__ = property(lambda self: self.func.__name__)


print spam.__doc__
# => spam doc string

print spam.__name__
# => spam


初めてのPython 第3版

初めてのPython 第3版

エキスパートPythonプログラミング

エキスパートPythonプログラミング