文章目录

Python 装饰器链:多个装饰器的执行顺序

发布于 2026-04-03 16:18:26 · 浏览 4 次 · 评论 0 条

Python 装饰器链:多个装饰器的执行顺序

当你在一个函数上叠加多个装饰器时,Python 会按照特定顺序应用它们。理解这个顺序对调试和设计装饰逻辑至关重要。


理解装饰器链的基本规则

装饰器链的书写形式如下:

@decorator_a
@decorator_b
@decorator_c
def my_function():
    pass

实际执行顺序是:最靠近函数定义的装饰器最先执行,最外层的最后执行。也就是说,上述代码等价于:

my_function = decorator_a(decorator_b(decorator_c(my_function)))

因此:

  • decorator_c 先包裹原始函数;
  • decorator_b 再包裹 decorator_c 的结果;
  • decorator_a 最后包裹整个结构。

动手验证执行顺序

创建三个简单的装饰器来打印调用时机:

def decorator_a(func):
    print("装饰器 A 开始")
    def wrapper(*args, **kwargs):
        print("A: 函数调用前")
        result = func(*args, **kwargs)
        print("A: 函数调用后")
        return result
    print("装饰器 A 结束")
    return wrapper

def decorator_b(func):
    print("装饰器 B 开始")
    def wrapper(*args, **kwargs):
        print("B: 函数调用前")
        result = func(*args, **kwargs)
        print("B: 函数调用后")
        return result
    print("装饰器 B 结束")
    return wrapper

def decorator_c(func):
    print("装饰器 C 开始")
    def wrapper(*args, **kwargs):
        print("C: 函数调用前")
        result = func(*args, **kwargs)
        print("C: 函数调用后")
        return result
    print("装饰器 C 结束")
    return wrapper

定义一个被三重装饰的函数:

@decorator_a
@decorator_b
@decorator_c
def greet(name):
    print(f"你好, {name}!")

运行这段代码(不调用 greet),你会看到输出:

装饰器 C 开始
装饰器 C 结束
装饰器 B 开始
装饰器 B 结束
装饰器 A 开始
装饰器 A 结束

这说明在函数定义阶段,装饰器是从下往上(即从 @decorator_c@decorator_a)依次执行的。

现在调用 greet("Alice")

greet("Alice")

输出为:

A: 函数调用前
B: 函数调用前
C: 函数调用前
你好, Alice!
C: 函数调用后
B: 函数调用后
A: 函数调用后

可以看到:

  • 进入时:最外层装饰器(A)最先执行其“前”逻辑;
  • 深入时:逐层向内,直到原始函数;
  • 返回时:按相反顺序执行“后”逻辑。

记忆口诀与核心结论

记住这个口诀

“定义时从下往上包,调用时从上往下进,返回时从下往上出。”

更精确地说:

  • 装饰阶段(定义函数时):从最靠近函数的装饰器开始,向上依次应用。
  • 调用阶段(运行函数时):最外层装饰器最先执行其前置逻辑,然后层层向内;函数执行完后,再从最内层向外执行后置逻辑。

下面表格总结了关键行为:

阶段 执行顺序(以 @A @B @C def f() 为例)
装饰阶段 C → B → A
调用进入 A → B → C → f
调用返回 f → C → B → A

实际应用场景示例

假设你需要同时实现日志记录、权限检查和性能计时。合理的装饰器链可能是:

@log_calls          # 最外层:记录整体调用
@require_auth       # 中间层:检查权限
@measure_time       # 最内层:精确测量函数执行时间
def process_data(data):
    # 处理数据
    pass

这样设计的原因是:

  • measure_time 必须紧贴函数,避免把权限检查或日志开销算进执行时间;
  • require_auth 在时间测量之后、业务逻辑之前执行,确保未授权用户不会触发任何处理;
  • log_calls 包裹全部逻辑,记录完整的请求生命周期。

编写这三个装饰器的骨架:

import time
import functools

def log_calls(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print(f"[LOG] 调用 {func.__name__}")
        result = func(*args, **kwargs)
        print(f"[LOG] {func.__name__} 完成")
        return result
    return wrapper

def require_auth(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        if not current_user_is_admin():  # 假设存在此函数
            raise PermissionError("需要管理员权限")
        return func(*args, **kwargs)
    return wrapper

def measure_time(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        duration = time.time() - start
        print(f"[TIME] {func.__name__} 耗时 {duration:.4f} 秒")
        return result
    return wrapper

按照上述顺序装饰,能确保时间测量只包含 process_data 本身的执行,而不包括权限检查和日志记录的开销。


常见错误与避坑指南

  1. 忘记使用 functools.wraps
    如果不使用 @functools.wraps(func),被装饰函数的元信息(如 __name__, __doc__)会被覆盖为 wrapper,导致调试困难或文档工具失效。

  2. 混淆装饰顺序导致逻辑错误
    例如,把 @measure_time 放在最外层,会导致它测量的是包括权限检查在内的总时间,而非函数本身。

  3. 在装饰器中修改参数但顺序不当
    如果某个装饰器依赖另一个装饰器预处理的参数,必须确保前者在外层、后者在内层,否则参数还未被处理。

检查你的装饰器链是否合理:问自己——“我希望哪个逻辑最先/最后执行?” 然后根据调用时的进入顺序(外→内)安排装饰器位置。


调试技巧:可视化装饰器堆栈

添加一个通用的调试装饰器来打印当前堆栈:

def debug_wrap(name):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            print(f"→ 进入 {name}")
            result = func(*args, **kwargs)
            print(f"← 退出 {name}")
            return result
        return wrapper
    return decorator

使用它包裹你的函数:

@debug_wrap("Logger")
@debug_wrap("Auth")
@debug_wrap("Timer")
def example():
    print("执行业务逻辑")

调用 example() 将清晰显示嵌套顺序:

→ 进入 Logger
→ 进入 Auth
→ 进入 Timer
执行业务逻辑
← 退出 Timer
← 退出 Auth
← 退出 Logger

这种模式能快速验证你的装饰器链是否按预期工作。

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文