Pythonにおける高階関数デコレータとは、別の関数(またはクラス)を受け取り、新しい関数(または新しく修正されたクラス)を返す関数です。デコレータは、既存の関数に対して追加のロジック(例えば、ログ記録、キャッシュ、権限チェックなど)を追加する「ラッピング」パターンを実現するためにしばしば使用されます。
元の関数の名前、ドキュメント、およびその他のメタデータを保持するためには、functools.wraps関数の使用が推奨されます:
import functools def log_decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): print(f'Calling {func.__name__}') return func(*args, **kwargs) return wrapper @log_decorator def add(a, b): """二つの数を加える""" return a + b print(add(2, 3)) # 出力: Calling add 5 print(add.__name__) # 出力: add print(add.__doc__) # 出力: 二つの数を加える
重要な点:functools.wrapsを使用しなければ、ラップされた関数は元のメタデータの名前、ドキュメントを失い、デバッグおよび自動ドキュメンテーションに悪影響を及ぼします。
functools.wrapsを使用せずに関数にデコレートすると、__name__や__doc__属性に何が起こりますか?
**回答:**それらは内部のラップ関数(通常は'wrapper')から継承され、元のメタデータは失われます。
def simple_decorator(func): def wrapper(*args, **kwargs): return func(*args, **kwargs) return wrapper @simple_decorator def f(): """これはドックストリングです""" pass print(f.__name__) # 出力: 'wrapper' (ではなく 'f') print(f.__doc__) # 出力: None (ではなく 'これはドックストリングです')
物語
プロジェクトでAPIエンドポイントのログ記録のために複雑なデコレータシステムを実装しましたが、functools.wrapsを使用しませんでした。その結果、自動生成されたドキュメンテーション(Swagger/OpenAPI)やインスペクションツールがすべてのエンドポイントの名前を'wrapper'として表示し、ドキュメントが消えました。これにより、サポート、テスト、メンテナンスが非常に困難になりました。
物語
pytestを使用してユニットテストを書いているときに、テストの自動検出が失敗しました:自分のデコレータを使用してデコレートされたテスト関数が検出されず、なぜならその__name__が不正だったからです。原因は、pytestが関数を名前で探しているからです。
物語
例外のスタックトレースをトレースする際、スタックトレースは常に「wrapper」を指し、どの関数がエラーを引き起こしたかを理解することができませんでした。なぜなら、ユーザーデコレータにfunctools.wrapsが欠如していたため、ルートメタデータが失われたからです。