当前位置: 首页 > news >正文

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

1. 引言

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

2. 装饰器的顺序

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

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

def decorator_a(func):print('decorator_a')def wrapper(*args, **kwargs):result = func(*args, **kwargs)return resultreturn wrapperdef decorator_b(func):print('decorator_b')def wrapper(*args, **kwargs):result = func(*args, **kwargs)return resultreturn wrapper@decorator_a
@decorator_b
def say_hello(name):print(f"Hello, {name}!")say_hello("Alice")

执行结果:

decorator_b
decorator_a
Hello, 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 resultreturn wrapperdef 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 resultreturn wrapper@decorator_a
@decorator_b
def say_hello(name):print(f"Hello, {name}!")say_hello("Alice")

执行结果为:

decorator b
decorator_a
Decorator A: Before function call
Decorator B: Before function call
Hello, Alice!
Decorator B: After function call
Decorator 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 resultreturn wrapper# @decorator_test
def 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 resultreturn wrapperdef 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 resultreturn wrapper# @decorator_test
def say_hello(name):print(f"Hello, {name}!")say_hello = decorator_test(decorator_test2(say_hello))
say_hello("Alice")print('-' * 30)@decorator_test
@decorator_test2
def say_hello2(name):print(f"Hello, {name}!")say_hello2("Alice2")

运行结果:

decorator_b
decorator_a
Decorator A: Before function call
Decorator B: Before function call
Hello, Alice!
Decorator B: After function call
Decorator A: After function call
------------------------------
decorator_b
decorator_a
Decorator A: Before function call
Decorator B: Before function call
Hello, Alice2!
Decorator B: After function call
Decorator A: After function call

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

在这里插入图片描述

image-20241104172532302

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

4. 结论

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


http://www.mrgr.cn/news/65710.html

相关文章:

  • 大学适合学C语言还是Python?
  • npm入门教程18:npm发布npm包
  • 关于Linux系统调试和性能优化技巧有哪些?
  • 数据挖掘(八)
  • 【JAVA】第3关:素数链
  • 一键安装python3
  • 并发编程(6)——future、promise、async,线程池
  • 写给粉丝们的信
  • 使用 MySQL Workbench 创建和管理用户
  • 六款高颜值注册页面(可复制源码)
  • 数据仓库设计-分层
  • 【数学二】线性代数-矩阵-分块矩阵及方阵的行列式
  • C++ 内存对齐:alignas 与 alignof
  • 24/11/4 算法笔记 蛇形卷积
  • redis:list列表命令和内部编码
  • 11.4工作笔记
  • 【AI+教育】一些记录@2024.11.04
  • 数据结构---链表实现栈
  • 内置函数【MySQL】
  • Java环境下配置环境(jar包)并连接mysql数据库
  • VisionPro —— CogPatInspectTool对比工具
  • 优选算法精品——双指针
  • 慢SQL优化方向
  • 今日 AI 简报|AI 提示词管理、端到端语音处理、会议助手、大模型集合平台等前沿技术集中亮相
  • LeetCode 0633.平方数之和:模拟
  • Linux之初体验