Python中的装饰器是一种非常实用的语法特性,可以在不修改原函数代码的前提下扩展函数的功能。当同一个函数被多个装饰器修饰时,其执行顺序遵循固定的规则,理解这个规则能帮助我们更好地使用装饰器实现复杂的功能扩展。

装饰器的基本工作原理
装饰器的本质是一个接收函数作为参数,并返回一个新函数的高阶函数。当我们使用@装饰器名语法修饰函数时,等价于将原函数作为参数传入装饰器,再用装饰器返回的新函数替换原函数。
下面是一个最简单的装饰器示例:
def simple_decorator(func):
def wrapper():
print("装饰器前置逻辑")
func()
print("装饰器后置逻辑")
return wrapper
@simple_decorator
def test_func():
print("原函数逻辑")
# 等价于 test_func = simple_decorator(test_func)
test_func()
上述代码执行后会依次输出:装饰器前置逻辑、原函数逻辑、装饰器后置逻辑,说明装饰器会在原函数执行前后插入自定义逻辑。
多个装饰器叠加的执行顺序规则
当同一个函数被多个装饰器修饰时,装饰器的加载顺序是从下到上,而执行顺序是从上到下。也就是说,离函数定义最近的装饰器会先被加载,但是它的内部逻辑会在所有装饰器加载完成后,按照从外到内的顺序执行。
加载顺序验证
我们可以通过在装饰器内部添加打印语句来验证加载顺序,示例代码如下:
def decorator_a(func):
print("装饰器A加载完成")
def wrapper():
print("进入装饰器A的逻辑")
func()
print("离开装饰器A的逻辑")
return wrapper
def decorator_b(func):
print("装饰器B加载完成")
def wrapper():
print("进入装饰器B的逻辑")
func()
print("离开装饰器B的逻辑")
return wrapper
@decorator_a
@decorator_b
def multi_decorator_func():
print("原函数执行")
print("开始调用函数")
multi_decorator_func()
上述代码的输出结果如下:
装饰器B加载完成 装饰器A加载完成 开始调用函数 进入装饰器A的逻辑 进入装饰器B的逻辑 原函数执行 离开装饰器B的逻辑 离开装饰器A的逻辑
从输出可以看到,先打印的是装饰器B加载完成,再打印装饰器A加载完成,说明加载顺序是从下到上,先加载离函数近的decorator_b,再加载decorator_a。
执行顺序原理分析
多个装饰器叠加的语法本质是嵌套调用,@decorator_a @decorator_b def func()等价于:
# 先执行 decorator_b(func),得到b_wrapper b_wrapper = decorator_b(multi_decorator_func) # 再执行 decorator_a(b_wrapper),得到a_wrapper a_wrapper = decorator_a(b_wrapper) # 最终 multi_decorator_func 指向 a_wrapper multi_decorator_func = a_wrapper
当我们调用multi_decorator_func()时,实际是调用a_wrapper(),而a_wrapper内部的func()调用的是b_wrapper(),b_wrapper内部的func()才是真正的原函数,因此执行顺序就是先走decorator_a的前置逻辑,再走decorator_b的前置逻辑,然后执行原函数,之后走decorator_b的后置逻辑,最后走decorator_a的后置逻辑。
带参数的装饰器叠加顺序
如果装饰器本身带参数,执行顺序的规则依然不变,只是加载时需要先传入参数再处理原函数。示例代码如下:
def decorator_with_args(arg):
print(f"带参数装饰器加载,参数:{arg}")
def outer(func):
def wrapper():
print(f"进入带参数装饰器,参数:{arg}")
func()
print(f"离开带参数装饰器,参数:{arg}")
return wrapper
return outer
@decorator_with_args("A")
@decorator_with_args("B")
def func_with_args():
print("带参数的原函数执行")
func_with_args()
上述代码的输出结果如下:
带参数装饰器加载,参数:B 带参数装饰器加载,参数:A 进入带参数装饰器,参数:A 进入带参数装饰器,参数:B 带参数的原函数执行 离开带参数装饰器,参数:B 离开带参数装饰器,参数:A
可以看到带参数的装饰器同样遵循加载从下到上,执行从上到下的规则。
注意事项
- 装饰器的加载发生在函数定义阶段,也就是模块导入的时候就会执行装饰器的外层逻辑,而不是函数调用的时候。
- 如果多个装饰器之间有依赖关系,需要根据执行顺序合理排列装饰器的位置,避免出现逻辑错误。
- 使用
functools.wraps可以保留原函数的元信息,在多个装饰器叠加时也需要为每个wrapper函数添加该装饰器,避免原函数的名称、文档等信息被覆盖。
以下是一个使用functools.wraps的正确示例:
import functools
def decorator_one(func):
@functools.wraps(func)
def wrapper():
print("装饰器1逻辑")
func()
return wrapper
def decorator_two(func):
@functools.wraps(func)
def wrapper():
print("装饰器2逻辑")
func()
return wrapper
@decorator_one
@decorator_two
def demo():
print("demo函数")
print(demo.__name__) # 输出 demo,而不是wrapper