前回、Pythonのカスタムデコレーターについて学びました。
今回はその続きとして、@functools.wraps というデコレーターの中でよく使われる仕組みについて、自分なりに整理してみます。
まだデコレーターの仕組み自体がよく分からない方は、先にそちらを確認すると分かりやすいと思います。
どうして wraps が必要なの?
まずは何もつけずにカスタムデコレーターを使った場合の例です。
def my_decorator(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@my_decorator
def greet():
"""挨拶をする関数"""
print("こんにちは!")
print(greet.__name__)
print(greet.__doc__)
実行結果:
wrapper
None
なぜこうなるの?
-
@my_decoratorによって、greetの中身がwrapperに置き換わる - その結果、
__name__も__doc__もwrapperのものになってしまう
関数の名前や説明(ドキュメント文字列)が上書きされてしまうのは、後から関数を調べたり、ツールで扱ったりするときに困ることがあります。
functools.wraps で解決できる!
そこで出てくるのが functools.wraps です。
これは元の関数の情報(名前・docstringなど)を保持するための「補助的なデコレーター」です。
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("こんにちは!")
print(greet.__name__)
print(greet.__doc__)
実行結果:
greet
挨拶をする関数
今度はちゃんと greet という名前と docstring が保持されています!
まとめ:wraps の役割とポイント
| 項目 | 内容 |
|---|---|
| 機能 | 元の関数の情報(__name__, __doc__ など)をラッパー関数にコピーする |
| 使用場所 | デコレーター内の wrapper 関数に @wraps(func) をつける |
| 必須か? | デコレーターの機能には関係ないが、ベストプラクティスとして使うべき |
| インポート方法 | from functools import wraps |
おわりに
@wraps は、デコレーターを使うときによく一緒に見かけるものですが、最初は「なんでこれがいるの?」とよく分かりませんでした。
ですが実際に __name__ や __doc__ が失われる例を見てみると、「これはあったほうがいいな」と納得できました。