デコレータを適用すると、元の関数の __name__ や __doc__ がラッパー関数のものに置き換わってしまいます。functools.wraps はこの問題を解決します。
問題の確認
まず、wraps を使わない場合の挙動を見てみましょう。
def my_decorator(func): def wrapper(*args, **kwargs): return func(*args, **kwargs) return wrapper @my_decorator def greet(): """挨拶を表示する関数""" print("Hello!") print(greet.__name__) # wrapper print(greet.__doc__) # None
デコレートされた関数の名前が「wrapper」になり、docstring も消えています。これではデバッグや introspection に支障が出ます。
functools.wraps による解決
functools.wraps を使うと、元の関数のメタデータがラッパーにコピーされます。
from functools import wraps def my_decorator(func): @wraps(func) def wrapper(*args, **kwargs): return func(*args, **kwargs) return wrapper @my_decorator def greet(): """挨拶を表示する関数""" print("Hello!") print(greet.__name__) # greet print(greet.__doc__) # 挨拶を表示する関数
wraps がコピーする属性は __name__, __doc__, __module__, __qualname__, __annotations__, __dict__ です。また、__wrapped__ 属性に元の関数への参照も設定されます。
print(greet.__wrapped__) # <function greet at 0x...>
デコレータを自作する際は、常に functools.wraps を使う習慣をつけましょう。