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


デコレータは、関数の外側に再利用できる動作を巻き付けます。ログ、計測、リトライ、権限チェック、トレースのように、多くの関数で同じ処理が必要なときに向いています。
準備するもの
Section titled “準備するもの”- 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__)実行します。
python decorator_demo.py期待される出力:
[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 で関数名を維持できることが分かります。
順序を変えてみる
Section titled “順序を変えてみる”デコレータを次の順に入れ替えます。
@retry(max_retries=2)@log_calldef fetch_model_info(model_id):この場合、ログは各リトライの内側で実行されます。サービスコードでは、デコレータの順序が重要です。
デコレータを使う場面
Section titled “デコレータを使う場面”向いているもの:
- ログとトレース
- 実行時間の計測
- 不安定な I/O のリトライ
- 権限チェック
- フレームワークへの登録
ラッパーが重要な業務ロジックを隠してしまう場合や、すでに層が多すぎる関数には向きません。
このページを終えたら、この証拠カードを残します。
- Pythonパターン
- デコレータ、イテレータ、ジェネレータ、並行処理プリミティブ、またはメタプログラミングフック
- コード成果物
- 最小限の実行可能な例と表示された出力
- 使用場面
- この pattern が AI app、pipeline、tool、または server を改善する場面
- 失敗確認
- 隠れた副作用、読みにくい抽象化、競合状態、または過度な設計
- 期待される成果
- 実践的なAIシステム用途のメモを含む小さな高度Python例
よくある間違い
Section titled “よくある間違い”@wrapsを忘れ、ログやフレームワークからすべてwrapperに見えてしまう。- 検証エラーや権限エラーのように即失敗すべき例外までリトライする。
- デコレータを積みすぎて、実行順序を追いにくくする。
fetch_model_info の前に require_role("admin") デコレータを追加してください。admin 以外なら PermissionError を出し、権限エラーはリトライしないようにします。
参考実装と解説
よい実装では、権限チェックを先に扱い、retry は一時的な失敗だけを扱うようにします。require_role("admin") をリトライ経路の外側に置くか、retry が PermissionError を受け取ったらすぐ再送出するようにします。
期待される挙動は次の通りです。
- admin ユーザーは通常どおり関数を呼び出せる。
- admin 以外は
PermissionErrorになる。 - 権限エラーは一時的なネットワーク障害ではないので、retry ログが繰り返されない。