コンテンツにスキップ

E.B.1 デコレータの応用

Python デコレータ実行フロー図

デコレータによる横断的ロジックの階層図

デコレータは、関数の外側に再利用できる動作を巻き付けます。ログ、計測、リトライ、権限チェック、トレースのように、多くの関数で同じ処理が必要なときに向いています。

  • Python 3.10+
  • 外部パッケージ不要
  • 関数の基本理解
  • Wrapper(ラッパー):元の関数の前後で実際に動く内側の関数。
  • Cross-cutting logic(横断的ロジック):多くの場所で必要だが、業務処理そのものではないロジック。
  • functools.wraps:デコレート後も元の関数名やメタ情報を残す。
  • デコレータの順序:関数呼び出し時は、一番上のデコレータから実行される。

ログとリトライのデコレータを動かす

Section titled “ログとリトライのデコレータを動かす”

decorator_demo.py を作成します。

from functools import wraps
def log_call(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
print(f"[LOG] start {fn.__name__}")
result = fn(*args, **kwargs)
print(f"[LOG] end {fn.__name__}")
return result
return wrapper
def retry(max_retries=2):
def decorator(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
last_error = None
for attempt in range(1, max_retries + 2):
try:
return fn(*args, **kwargs)
except RuntimeError as error:
last_error = error
print(f"[RETRY] attempt={attempt} error={error}")
raise last_error
return wrapper
return decorator
state = {"attempt": 0}
@log_call
@retry(max_retries=2)
def fetch_model_info(model_id):
state["attempt"] += 1
if state["attempt"] < 2:
raise RuntimeError("temporary network error")
return {"model_id": model_id, "status": "ready"}
print(fetch_model_info("demo-v1"))
print(fetch_model_info.__name__)

実行します。

Terminal window
python decorator_demo.py

期待される出力:

Terminal window
[LOG] start fetch_model_info
[RETRY] attempt=1 error=temporary network error
[LOG] end fetch_model_info
{'model_id': 'demo-v1', 'status': 'ready'}
fetch_model_info

この例では、業務関数を短く保ち、リトライを一箇所にまとめ、wraps で関数名を維持できることが分かります。

デコレータを次の順に入れ替えます。

@retry(max_retries=2)
@log_call
def fetch_model_info(model_id):

この場合、ログは各リトライの内側で実行されます。サービスコードでは、デコレータの順序が重要です。

向いているもの:

  1. ログとトレース
  2. 実行時間の計測
  3. 不安定な I/O のリトライ
  4. 権限チェック
  5. フレームワークへの登録

ラッパーが重要な業務ロジックを隠してしまう場合や、すでに層が多すぎる関数には向きません。

このページを終えたら、この証拠カードを残します。

Pythonパターン
デコレータ、イテレータ、ジェネレータ、並行処理プリミティブ、またはメタプログラミングフック
コード成果物
最小限の実行可能な例と表示された出力
使用場面
この pattern が AI app、pipeline、tool、または server を改善する場面
失敗確認
隠れた副作用、読みにくい抽象化、競合状態、または過度な設計
期待される成果
実践的なAIシステム用途のメモを含む小さな高度Python例
  • @wraps を忘れ、ログやフレームワークからすべて wrapper に見えてしまう。
  • 検証エラーや権限エラーのように即失敗すべき例外までリトライする。
  • デコレータを積みすぎて、実行順序を追いにくくする。

fetch_model_info の前に require_role("admin") デコレータを追加してください。admin 以外なら PermissionError を出し、権限エラーはリトライしないようにします。

参考実装と解説

よい実装では、権限チェックを先に扱い、retry は一時的な失敗だけを扱うようにします。require_role("admin") をリトライ経路の外側に置くか、retryPermissionError を受け取ったらすぐ再送出するようにします。

期待される挙動は次の通りです。

  • admin ユーザーは通常どおり関数を呼び出せる。
  • admin 以外は PermissionError になる。
  • 権限エラーは一時的なネットワーク障害ではないので、retry ログが繰り返されない。