複数のデコレータを重ねた場合、適用順序と実行順序が異なります。この仕組みを理解しないと、予期しない動作が起きます。
デコレータの適用順序
デコレータは下から上に適用されます。
@decorator_a @decorator_b @decorator_c def func(): pass # 等価な記述 func = decorator_a(decorator_b(decorator_c(func)))
最も内側(func に近い)の decorator_c が最初に適用されます。
実行順序
ラッパー関数の実行は、適用の逆順(上から下)になります。
def decorator(name): def wrapper(func): def inner(*args, **kwargs): print(f"{name}: before") result = func(*args, **kwargs) print(f"{name}: after") return result return inner return wrapper @decorator("A") @decorator("B") @decorator("C") def greet(): print("Hello!") greet()
出力:
A: before
B: before
C: before
Hello!
C: after
B: after
A: after
外側のデコレータから実行が始まり、内側に向かい、戻るときは逆順です。
順序が重要な例
from functools import wraps def require_auth(func): @wraps(func) def wrapper(*args, **kwargs): print("Checking authentication") return func(*args, **kwargs) return wrapper def log_call(func): @wraps(func) def wrapper(*args, **kwargs): print(f"Calling {func.__name__}") return func(*args, **kwargs) return wrapper # ログを先に出したい場合 @log_call @require_auth def action(): print("Action executed") action() # Calling wrapper ← log_call が wrapper を見ている # Checking authentication # Action executed
この例では log_call が require_auth のラッパーを見てしまいます。
@wraps の効果
wraps を使えば、関数名が保持されます。
# wraps を使った場合の出力 # Calling action # Checking authentication # Action executed
デコレータの順序を決める際は、実行順序を意識して設計しましょう。