Python の functools.wraps の役割と使い方

デコレータを適用すると、元の関数の __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 を使う習慣をつけましょう。