Python のデコレータのスタック順序と実行順序
複数のデコレータを重ねた場合、適用順序と実行順序が異なります。この仕組みを理解しないと、予期しない動作が起きます。
デコレータの適用順序
デコレータは下から上に適用されます。
@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
デコレータの順序を決める際は、実行順序を意識して設計しましょう。