Python講座 / デコレーターとコンテキストマネージャー

デコレーターは関数の呼び出しを包み、コンテキストマネージャーはブロックの前後の準備と片付けを管理します。共通処理をまとめられますが、元の戻り値と例外の契約を意図して保つ必要があります。

呼び出しの拡張と寿命の管理を分ける

@decoratorは定義した関数をデコレーターへ渡し、その結果を関数名へ結び付ける実際の処理です。コメントではありません。withは開始と終了の処理を呼び、本文で例外が起きた場合の片付けも組み立てられます。

functools.wrapsは元の関数名や説明などのメタデータを保つために使います。それだけで型、安全性、例外処理などのすべての保証を保ったことにはなりません。

ラッパーと一時リストを実行する

wrappers.pyへ全体を保存し、python wrappers.pyを実行します。

from functools import wraps
from contextlib import contextmanager

def announce(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print("start")
        return func(*args, **kwargs)
    return wrapper

@contextmanager
def temporary_items():
    items = []
    try:
        yield items
    finally:
        items.clear()
        print("cleared")

@announce
def total(amounts):
    return sum(amounts)

with temporary_items() as items:
    items.extend([3500, 8000])
    print(total(items))
print(total.__name__)
print(items)

予想結果です。関数名はtotalのまま、withを抜けたリストは空になります。

start
11500
cleared
total
[]

値と例外が通る経路を追う

contextmanagerを使うジェネレーターは一回yieldして、with本文へ値を渡します。終了時はその後の処理へ戻り、finallyに置いた片付けは本文の失敗時にも動きます。例外を捕まえて消すと失敗を隠すため、意図した抑制でない限り伝えます。

ラッパーでreturnを忘れると、元の関数の結果ではなくNoneが返ります。メッセージが出たかだけでなく、引数の受け渡し、戻り値、例外の伝播を確認します。

後処理を試して抽象化を選ぶ

with本文で意図的にValueErrorを起こし、clearedが出てリストが片付けられることを確認します。別の練習でラッパーのreturnを一時的に外し、結果の変化を見てから元へ戻します。

再試行のデコレーターは状態を変えるリクエストを繰り返し、取引を重複させる場合があります。再試行の方針と資源の後処理を混同しないでください。一回だけの小さな操作なら、新しい仕組みを作らず書く方が明確な場合もあります。基になる流れは例外とfinallyで確認できます。

@announce は定義後の func = announce(func) に相当します。wrapsは名前や説明などのメタ情報を保ち、引数検証を代行しません。contextmanagerは開始処理→yieldでwith本文へ→正常終了または例外→finallyの片付けという順です。yield付近で例外を捕まえて再送出しないと抑制される場合があるため、意図した回復か確認します。

動作と適用条件の公式説明:Python: functools.wraps

関連する公式リファレンス