写点什么

Python 装饰器执行的顺序你知道吗

作者:LLLibra146
  • 2024-11-04
    北京
  • 本文字数:2625 字

    阅读完需:约 9 分钟

1. 引言

前面的文章中,讲到了 Python 装饰器的基础使用方式,在实际使用中,可能会遇到一个函数使用多个装饰器的情况,这个时候装饰器的顺序同样至关重要。本文将讨论装饰器的顺序如何影响函数的行为,并通过几个例子来说明。


2. 装饰器的顺序

当在一个函数上应用多个装饰器时,装饰器的执行顺序会影响最终的结果。


提到装饰器的顺序,很多人可能会说,装饰器是从内到外应用的,也就是说最靠近函数定义的装饰器会最先执行,而最外层的装饰器会最后执行。真的是这样吗,来看下面的例子:


def decorator_a(func):    print('decorator_a')
def wrapper(*args, **kwargs): result = func(*args, **kwargs) return result
return wrapper

def decorator_b(func): print('decorator_b')
def wrapper(*args, **kwargs): result = func(*args, **kwargs) return result
return wrapper

@decorator_a@decorator_bdef say_hello(name): print(f"Hello, {name}!")

say_hello("Alice")
复制代码


执行结果:


decorator_bdecorator_aHello, Alice!
复制代码


看起来好像真的是先执行了装饰器 b,后执行了装饰器 a。但是事实真的如此吗,来给装饰器实际执行逻辑打个日志。


看下面的例子:


def decorator_a(func):    print('decorator_a')
def wrapper(*args, **kwargs): print("Decorator A: Before function call") result = func(*args, **kwargs) print("Decorator A: After function call") return result
return wrapper

def decorator_b(func): print('decorator b')
def wrapper(*args, **kwargs): print("Decorator B: Before function call") result = func(*args, **kwargs) print("Decorator B: After function call") return result
return wrapper

@decorator_a@decorator_bdef say_hello(name): print(f"Hello, {name}!")

say_hello("Alice")
复制代码


执行结果为:


decorator bdecorator_aDecorator A: Before function callDecorator B: Before function callHello, Alice!Decorator B: After function callDecorator A: After function call
复制代码


这个时候的结果就比较有趣了。来分析一下日志,装饰器 a 和 b 两个装饰器的确是按照之前提到的逻辑执行了,但是装饰器实际的逻辑却和我们预想的不一样。


为什么会出现这样的结果呢?为了理解这个问题,先来说一下装饰器的原理。


Python 装饰器的实现依赖于函数的可调用性和闭包的概念。


  1. 函数可以作为参数传递:在 Python 中,函数是一等公民,可以像变量一样被传递和赋值。这使得我们可以将函数作为参数传递给装饰器函数。

  2. 闭包:装饰器函数内部定义了一个新的函数(通常称为包装函数),这个包装函数可以访问装饰器函数的参数以及外部函数的局部变量。当装饰器函数返回包装函数时,包装函数就携带了这些信息,形成了一个闭包。


来看一个小例子


def decorator_test(func):    print('decorator_a')
def wrapper(*args, **kwargs): print("Decorator A: Before function call") result = func(*args, **kwargs) print("Decorator A: After function call") return result
return wrapper

# @decorator_testdef say_hello(name): print(f"Hello, {name}!")

say_hello = decorator_test(say_hello)say_hello("Alice")
复制代码


在上面的例子中,我注释了 @后面的装饰器,增加了一个赋值语句,其实,我们在用 @的时候就相当于执行了这个赋值语句,只不过用 @会简化我们的操作,同时更加直观易于理解。所以到这里我们应该能明白了,装饰器说白了就是将当前的函数当做参数传递给装饰器函数,然后装饰器函数执行后返回一个新的函数,这个函数替代了我们之前的函数,然后在真正调用函数的时候就会发现装饰器函数被执行了。


理解了上述原理以后,就会有一个问题了,既然函数会被当做参数传递,那么如果有两个装饰器的话,应该先传递谁呢,因为先传递的一定是原来的函数,后传递的已经是被第一个装饰器装饰过的函数了。


来看一个例子。


def decorator_test(func):    print('decorator_a')
def wrapper(*args, **kwargs): print("Decorator A: Before function call") result = func(*args, **kwargs) print("Decorator A: After function call") return result
return wrapper

def decorator_test2(func): print('decorator_b')
def wrapper(*args, **kwargs): print("Decorator B: Before function call") result = func(*args, **kwargs) print("Decorator B: After function call") return result
return wrapper

# @decorator_testdef say_hello(name): print(f"Hello, {name}!")

say_hello = decorator_test(decorator_test2(say_hello))say_hello("Alice")
print('-' * 30)

@decorator_test@decorator_test2def say_hello2(name): print(f"Hello, {name}!")

say_hello2("Alice2")
复制代码


运行结果:


decorator_bdecorator_aDecorator A: Before function callDecorator B: Before function callHello, Alice!Decorator B: After function callDecorator A: After function call------------------------------decorator_bdecorator_aDecorator A: Before function callDecorator B: Before function callHello, Alice2!Decorator B: After function callDecorator A: After function call
复制代码


从上面的例子可以看出,两种方式的运行结果是一致的,也就是说,say_hello = decorator_test(decorator_test2(say_hello)) 等价于 say_hello2 函数的装饰器写法。




从上面两个图可以看到,装饰器函数本身是按照最靠近函数的优先执行的顺序执行的,但是 wrapper 函数是一层一层从最靠近函数的顺序嵌套执行的,也就是说,最外层的函数最先被执行,执行之后执行第二个装饰器,依次往最内层执行,然后依次返回,可以参考上图的数字序号。

4. 结论

综上所述,装饰器的顺序不能一概而论说内层装饰器先执行,准确的说应该是从外到内一层一层依次执行的,外层装饰器先执行,但是最后执行完,内层装饰器后执行,但是先执行完毕。有点类似于八股文, 大家把上面的代码执行一遍,打个断点跟一遍调试基本上就能明白了。

发布于: 刚刚阅读数: 4
用户头像

LLLibra146

关注

还未添加个人签名 2018-09-17 加入

还未添加个人简介

评论

发布
暂无评论
Python装饰器执行的顺序你知道吗_Python_LLLibra146_InfoQ写作社区