大数跨境
0
0

Python 多个装饰器执行顺序:代码验证 + 规律总结,新手也能看懂

Python 多个装饰器执行顺序:代码验证 + 规律总结,新手也能看懂 码途钥匙
2025-10-20
0


Python 开发中,我们常给一个函数 / 类同时加多个装饰器(比如既加日志装饰器,又加缓存装饰器)。但很多人搞不懂:多个装饰器到底是 “先执行哪个”?是 “从上到下” 还是 “从下到上”?

比如下面这段代码,@decorator1和@decorator2的执行顺序是什么?调用func()时,两个装饰器的逻辑会以怎样的顺序触发?

@decorator1@decorator2def func():print("执行原函数")

今天就通过 “自定义装饰器 + 代码验证” 的方式,拆解多个装饰器的装饰阶段调用阶段执行顺序,再总结规律与避坑点,让你彻底搞懂多装饰器的执行逻辑。




一、先明确:装饰器的两个核心阶段



在分析多个装饰器前,必须先明确一个关键概念:装饰器的执行分为 “装饰阶段” 和 “调用阶段”,这两个阶段的执行顺序完全不同。

1.装饰阶段:Python 解释器加载代码时,会自动执行装饰器函数,对原函数进行 “包装”(此时原函数还没被调用);

2.调用阶段:当我们主动调用被装饰后的函数时,才会执行装饰器中定义的额外逻辑(如日志、缓存)和原函数。

比如一个简单的装饰器:

def my_decorator(func):print("执行装饰器(装饰阶段)")def wrapper():print("执行装饰器逻辑(调用阶段)")func()return wrapper@my_decoratordef test():print("执行原函数")# 此时还没调用test(),但会先打印“执行装饰器(装饰阶段)”# 调用test()时,才会打印“执行装饰器逻辑(调用阶段)”和“执行原函数”

理解 “装饰阶段” 和 “调用阶段” 的分离,是搞懂多装饰器顺序的前提。




二、多个装饰器执行顺序:代码验证



我们通过自定义两个简单的装饰器,用 “打印日志” 的方式,直观验证多装饰器在 “装饰阶段” 和 “调用阶段” 的执行顺序。

步骤 1:定义两个装饰器

先定义decorator1和decorator2,每个装饰器都在 “装饰阶段” 和 “调用阶段” 打印明显的日志,方便区分:

      
      
      
# 装饰器1:打印装饰阶段和调用阶段的日志def decorator1(func):print("=== 执行decorator1的装饰逻辑(装饰阶段)===")def wrapper():print("=== 执行decorator1的前置逻辑(调用阶段)===")func()  # 调用下一层函数(可能是decorator2的wrapper,或原函数)print("=== 执行decorator1的后置逻辑(调用阶段)===")return wrapper# 装饰器2:结构与decorator1一致,日志区分标识def decorator2(func):print("=== 执行decorator2的装饰逻辑(装饰阶段)===")def wrapper():print("=== 执行decorator2的前置逻辑(调用阶段)===")func()  # 调用下一层函数(原函数)print("=== 执行decorator2的后置逻辑(调用阶段)===")return wrapper步骤 2:给函数加两个装饰器用@decorator1和@decorator2装饰test_func函数,观察执行结果:# 先写@decorator1,再写@decorator2(注意顺序)@decorator1@decorator2def test_func():print("=== 执行原函数test_func ===")# 此时代码刚加载,还没调用test_func(),先看“装饰阶段”的输出

执行结果(装饰阶段):

      
      
      
=== 执行decorator2的装饰逻辑(装饰阶段)===
=== 执行decorator1的装饰逻辑(装饰阶段)===

关键结论(装饰阶段):

多个装饰器的装饰顺序是 “从下到上”(即 “靠近原函数的装饰器先执行”):

  • 先执行@decorator2(靠近原函数)的装饰逻辑;

  • 再执行@decorator1(远离原函数)的装饰逻辑。

可以理解为:装饰器的写法顺序是@d1在上、@d2在下,但装饰时是d2先 “包裹” 原函数,d1再 “包裹”d2的结果,形成 “d1→d2→原函数” 的嵌套结构。

步骤 3:调用被装饰后的函数

现在调用test_func(),观察 “调用阶段” 的执行顺序:

# 调用被装饰后的test_func()print("\n开始调用test_func():")test_func()

执行结果(调用阶段):

      
      
      
开始调用test_func():
=== 执行decorator1的前置逻辑(调用阶段)===
=== 执行decorator2的前置逻辑(调用阶段)===
=== 执行原函数test_func ===
=== 执行decorator2的后置逻辑(调用阶段)===
=== 执行decorator1的后置逻辑(调用阶段)===

关键结论(调用阶段):

多个装饰器的调用顺序是 “从上到下”(即 “远离原函数的装饰器先执行前置逻辑,后执行后置逻辑”):

1.先执行decorator1的前置逻辑;

2.再执行decorator2的前置逻辑;

3.然后执行原函数;

4.接着执行decorator2的后置逻辑;

5.最后执行decorator1的后置逻辑。

形象理解:调用时就像 “剥洋葱”,先剥开最外层的decorator1,再剥开decorator2,执行原函数后,再一层层 “包回去”。




三、原理拆解:为什么是这样的顺序?



多个装饰器的执行顺序,本质是 “函数嵌套” 和 “函数调用栈” 的逻辑。我们用代码还原装饰过程,就能明白背后的原理。

1. 装饰阶段:本质是 “多层函数嵌套”

当我们写:

@decorator1@decorator2def test_func():pass

等价于 Python 解释器执行:

# 第一步:用decorator2装饰原函数test_func,得到d2_wrapperd2_wrapper = decorator2(test_func)# 第二步:用decorator1装饰d2_wrapper,得到d1_wrapper(最终的test_func)test_func = decorator1(d2_wrapper)

所以装饰阶段的顺序是 “先 decorator2,再 decorator1”—— 先完成内层嵌套,再完成外层嵌套。

2. 调用阶段:本质是 “多层函数调用栈”

调用test_func()时,实际调用的是d1_wrapper,执行流程如下:

# 调用test_func() → 即调用d1_wrapper()def d1_wrapper():print("d1前置")# 调用d2_wrapper()d2_wrapper()print("d1后置")d2_wrapper()的执行def d2_wrapper():print("d2前置")# 调用原test_func()test_func()print("d2后置")# 原test_func()的执行def test_func():print("原函数")

所以调用阶段的顺序是 “d1 前置→d2 前置→原函数→d2 后置→d1 后置”—— 先执行外层装饰器的前置逻辑,再深入内层,最后从内层向外层执行后置逻辑。




四、扩展验证:带参数的装饰器顺序



上面的例子是 “无参数装饰器”,如果是 “带参数的装饰器”(如@decorator(arg)),执行顺序会变吗?我们再做一次验证。

步骤 1:定义带参数的装饰器

# 带参数的装饰器1:需先接收参数,再返回装饰器函数def decorator_with_arg1(arg):print(f"=== 执行decorator_with_arg1的参数处理(装饰阶段,参数:{arg})===")def actual_decorator(func):print("=== 执行decorator_with_arg1的实际装饰逻辑(装饰阶段)===")def wrapper():print("=== 执行decorator_with_arg1的前置逻辑(调用阶段)===")func()print("=== 执行decorator_with_arg1的后置逻辑(调用阶段)===")return wrapperreturn actual_decorator# 带参数的装饰器2:结构与1一致def decorator_with_arg2(arg):print(f"=== 执行decorator_with_arg2的参数处理(装饰阶段,参数:{arg})===")def actual_decorator(func):print("=== 执行decorator_with_arg2的实际装饰逻辑(装饰阶段)===")def wrapper():print("=== 执行decorator_with_arg2的前置逻辑(调用阶段)===")func()print("=== 执行decorator_with_arg2的后置逻辑(调用阶段)===")return wrapperreturn actual_decorator

步骤 2:装饰函数并验证

# 带参数的装饰器,顺序依然是@d1在上,@d2在下@decorator_with_arg1("hello")@decorator_with_arg2("world")def test_arg_func():print("=== 执行带参数装饰器的原函数 ===")# 先看装饰阶段输出,再调用函数看调用阶段输出print("\n开始调用test_arg_func():")test_arg_func()

执行结果(装饰阶段):

      
      
      
=== 执行decorator_with_arg1的参数处理(装饰阶段,参数:hello)===
=== 执行decorator_with_arg2的参数处理(装饰阶段,参数:world)===
=== 执行decorator_with_arg2的实际装饰逻辑(装饰阶段)===
=== 执行decorator_with_arg1的实际装饰逻辑(装饰阶段)===

执行结果(调用阶段):

      
      
      
开始调用test_arg_func():
=== 执行decorator_with_arg1的前置逻辑(调用阶段)===
=== 执行decorator_with_arg2的前置逻辑(调用阶段)===
=== 执行带参数装饰器的原函数 ===
=== 执行decorator_with_arg2的后置逻辑(调用阶段)===
=== 执行decorator_with_arg1的后置逻辑(调用阶段)===

关键结论(带参数装饰器):

1.参数处理阶段:顺序是 “从上到下”(先执行上层装饰器的参数处理,再执行下层);

2.实际装饰阶段:顺序仍是 “从下到上”(先执行下层装饰器的实际装饰逻辑);

3.调用阶段:顺序仍是 “从上到下”(先执行上层装饰器的前置逻辑)。

本质是:带参数的装饰器多了 “参数处理步骤”(先执行decorator_with_arg(arg)得到实际装饰器),但后续的 “实际装饰” 和 “调用” 顺序与无参数装饰器一致。




五、避坑指南:3 个常见错误与解决方案



1. 错误 1:混淆 “装饰顺序” 和 “调用顺序”

问题:误以为装饰顺序和调用顺序一致(比如认为 “@d1 在上,就先装饰 d1”),导致装饰逻辑执行不符合预期。

解决方案:记住 “两阶段” 规律:

  • 装饰阶段:从下到上(靠近原函数的先装饰);

  • 调用阶段:从上到下(远离原函数的先执行前置逻辑)。

2. 错误 2:带参数装饰器的参数处理顺序搞错

问题:在带参数装饰器中,误以为参数处理顺序是 “从下到上”,导致参数依赖逻辑出错。

解决方案:带参数装饰器的 “参数处理” 是 “从上到下”(先处理上层装饰器的参数),但 “实际装饰” 仍按 “从下到上”。

3. 错误 3:装饰器嵌套导致原函数元信息丢失

问题:多个装饰器嵌套后,func.__name__、func.__doc__等元信息丢失,调试困难。

解决方案:在每个装饰器的wrapper函数上添加@functools.wraps(func),保留原函数元信息。

      
      
      
import functoolsdef decorator1(func):@functools.wraps(func)  # 关键:保留原函数元信息def wrapper():func()return wrapperdef decorator2(func):@functools.wraps(func)  # 每个装饰器都要加def wrapper():func()return wrapper@decorator1@decorator2def test():"""测试函数"""passprint(test.__name__)  # 输出test(而非wrapper)print(test.__doc__)   # 输出“测试函数”(而非空)





六、总结:多个装饰器执行顺序速查表



为了方便记忆,我们将多个装饰器的执行顺序总结成一张表:


image.png终极记忆口诀:

装饰阶段 “从下到上” 包,调用阶段 “从上到下” 跑;

带参装饰先处理(参数),顺序依然 “上到下”。

掌握多个装饰器的执行顺序,能帮你在实际开发中灵活组合装饰器(如 “日志 + 缓存 + 权限校验”),让代码既简洁又符合逻辑。比如给接口函数加 “权限校验装饰器(@auth)” 和 “日志装饰器(@log)”,按@log在上、@auth在下的顺序,就能实现 “先记录日志,再校验权限” 的调用逻辑(符合业务场景)。

如果在实际使用中遇到其他问题,欢迎在评论区留言,我们一起探讨!


【声明】内容源于网络
0
0
码途钥匙
欢迎来到 Python 学习乐园!这里充满活力,分享前沿实用知识技术。新手或开发者,都能找到价值。一起在这个平台,以 Python 为引,开启成长之旅,探索代码世界,共同进步。携手 Python,共赴精彩未来,快来加入我们吧!
内容 992
粉丝 0
码途钥匙 欢迎来到 Python 学习乐园!这里充满活力,分享前沿实用知识技术。新手或开发者,都能找到价值。一起在这个平台,以 Python 为引,开启成长之旅,探索代码世界,共同进步。携手 Python,共赴精彩未来,快来加入我们吧!
总阅读1
粉丝0
内容992