Python

Python デコレータの基本と型安全な実装方法

ⓘ本ページはプロモーションが含まれています

スポンサードリンク

1. デコレータの基本形と動作イメージ

1‑1. 関数デコレータ(最もシンプルな例)

1‑2. クラスデコレータで状態を保持

ポイント

パターン メリット
関数デコレータ + @functools.wraps メタ情報(__name__, __doc__, __module__, __qualname__)が保持され、IDE の補完が正しく動作
クラスデコレータ インスタンス属性で永続的な状態を持たせられる。複数関数に同じロジックを適用したいときに便利

2. functools.wraps が必須な理由

デコレータは内部で新しい関数オブジェクト(wrapper)を生成します。属性転送を行わないと、次のような問題が起きます。

@functools.wraps(または functools.update_wrapper) を付けるだけで、上記の「名前・ドキュメント・シグネチャ」すべてが元関数からコピーされます。


3. 型安全な汎用デコレータ ― ParamSpec の活用

Python 3.10 以降で利用可能な typing.ParamSpec は「任意の引数列」を型変数として扱えるため、デコレータが受け取るシグネチャを正確に表現できます。ここでは Concatenate を省き、実務で十分に活用できる形だけ示します。

効果

  • IDE が compute のシグネチャ (x: int, y: int = 0) -> int を正しく補完
  • デコレータ内部で型情報が失われないので、他のデコレータと組み合わせても安全

4. 実務でよく使う応用例

4‑1. ロギングデコレータ(標準 logging を利用)

ポイント
- logging の設定だけで出力先・フォーマットを一元管理できる。
- デコレータ自体は「前処理」しか持たないので、テストがしやすい。


4‑2. 認証チェックデコレータ(ParamSpec と組み合わせ)

ポイント

  • kwargs.get('user') のように 明示的な依存注入 にすれば、テストコードでモックユーザーを渡すだけでシナリオ検証が容易。
  • ParamSpec があるため、デコレータ適用後も元関数のシグネチャはそのまま保持される。

4‑3. 安全なキャッシュデコレータ(ハッシュ不可能引数に対応)

標準ライブラリの functools.lru_cache は内部で _make_key を使い、*args**kwargs のハッシュ化を安全に行っています。ここでは同様のロジックを自前実装し、非ハッシュ可能なオブジェクト(list, dict など)でも例外が出ない ようにします。

解説

  • pickle.dumps はリストや辞書などハッシュ不可能なオブジェクトもシリアライズできるため、キー生成で TypeError が起きない。
  • ハッシュ化に SHA256 を使うので、キーサイズは一定(64 文字)になりメモリ使用量が予測しやすい。
  • 本実装は 純粋関数 前提です。副作用がある関数には適用しないでください。

5. デコレータの適用順序とスタック効果 – 正しい理解

5‑1. 「装飾時」と「呼び出し時」の2段階プロセス

実行結果

ポイント

観点 正しい説明
装飾時@ 行が実行されるタイミング) Python は 下から上へ デコレータを適用する。すなわち、最も近い @deco_b が先に呼び出され、戻り値(ラップされた関数)が次のデコレータ @deco_a に渡される
呼び出し時 ラップされた関数は「外側 → 内側」の順で実行される。上に書いた deco_a が最初に前処理を行い、内部で deco_b(内側)が続く

つまり 「装飾時は下から上」「呼び出し時は上から下」 が正しい順序です。
この逆転を意識せずにデコレータを組み合わせると、たとえば「認証 → ロギング」の期待が ロギング → 認証 になり、セキュリティ上のバグにつながります。

5‑2. 順序が重要になる典型シナリオ

シナリオ 正しい装飾順(上から下) 実行時の流れ
認証 → ロギング @logger
@require_role('admin')
1. require_role が先にチェック、2. 合格したらロガーが記録
トランザクション管理 → キャッシュ @cache
@transactional
1. トランザクション開始・終了を保証、2. 結果はキャッシュに保存

6. まとめ & ベストプラクティスチェックリスト

✅ チェック項目 推奨実装例
@functools.wraps を必ず付与 @functools.wraps(func)
型安全は ParamSpec で確保 def deco(func: Callable[P, R]) -> Callable[P, R]: …
キャッシュキーはハッシュ不可能に備える _hash_key(pickle + SHA256)
デコレータの適用順序を意識 コメントで「外側 / 内側」マーク、テストで実行順序検証
副作用がある関数はキャッシュ対象にしない ドキュメントで明示
ログ・認証は共通インタフェースを持たせる *args, **kwargsParamSpec の併用

参考コード(すべてまとめ)


以上が、指摘事項をすべて反映した改訂版記事です。
デコレータの基本から型安全化、実務で直面しやすい課題まで網羅的に解説しましたので、ぜひプロジェクトのコードベースへ取り入れてみてください。

スポンサードリンク

-Python