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.xdnf.cn/news/2024.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

嵌入式操作系统FreeRTOS文件详解

系列文章目录 嵌入式操作系统FreeRTOS文件详解 嵌入式操作系统FreeRTOS文件详解 系列文章目录FreeRTOS下载 FreeRTOS下载 官网下载解压后得到的文件,如下图所示: 打开图 1.3.1.2 中的 FreeRTOS 子文件夹,就能够看到 FreeRTOS 内核的文件&…

使用Jupyter Notebook进行数据科学项目

💓 博客主页:瑕疵的CSDN主页 📝 Gitee主页:瑕疵的gitee主页 ⏩ 文章专栏:《热点资讯》 使用Jupyter Notebook进行数据科学项目 Jupyter Notebook 简介 安装 Jupyter Notebook 创建和管理 Notebook 编写和运行代码 示例…

火山引擎VeDI数据服务平台:在电商场景中,如何解决API编排问题?

01 平台介绍 数据服务平台可以在保证服务高可靠性和高安全性的同时,为各业务线搭建数据服务统一出口,促进数据共享,为数据和应用之间建立了一座“沟通桥梁”。 同时,解决数据理解困难、异构、重复建设、审计运维困难等问题&#x…

C#进阶1

C#进阶1 本文章主要介绍C#的进阶知识,如反射,特性.... 参考视频链接 原码 文章目录 C#进阶1反射步骤泛型反射调用方法 获取属性 特性特性的定义步骤扩展枚举练习 反射 在 C# 中,反射(Reflection)是一种强大的机制&a…

【深度学习】合合信息:生成式AI时代的内容安全与系统构建

生成式AI时代的内容安全与系统构建 一、生成式 AI 的发展现状二、图像内容安全问题2.1、举几个伪造数字内容的例子2.1.1、谣言检测2.1.2、欺诈图像识别2.1.3、伪造信息 2.2、伪造文档/证照检测应用场景2.2.1、目前图像篡改主要涉及以下几个场景 2.3、合合信息伪造文档/证照检测…

软件系统安全保证措施,质量保证措施方案(Word原件套用)

系统安全保证措施是构建稳固防御体系的核心,旨在全方位保障信息系统的安全性。以下是对这七项措施的简要概述: 一、身份鉴别:采用多种认证方式,如密码、生物识别等,确保用户身份的准确无误,防止非法入侵。 …

gozero--环境安装和api语法

文章目录 前言环境安装安装go ctl安装protoc安装go-zero安装etcd配置环境变量安装插件 二、api语法说明syntaxtypeserverservicedoc命令转换 前言 go-zero是go语言的微服务框架,微服务内容很多,我希望我这一篇可以绘制出完整的地图,帮助需要…

vue中el-table显示文本过长提示

1.el-table设置轻提示:show-overflow-tooltip“true“,改变轻提示宽度

在VS中安装chatGPT

2、在VSCode中打开插件窗口 3、输入ChatGPT 4、这里有个ChatGPT中文版,就它了 5、安装 6、这时候侧边栏多了一个chatGPT分页图标,点击它 7、打个招呼 8、好像不行 9、看一下细节描述 10、根据要求按下按下快捷键 Ctrl Shift P 11、切换成国内模式 12、…

sublime可以写python吗

首先你需要安装一个Sublime Text(http://www.sublimetext.com/)和一个Python(https://www.python.org/downloads/), 接下来打开Sublime Text: 1、如下图所示,点击菜单栏中的Tools —> Buil…

初始Docker

概述: 容器,作为云原生技术的重要组成部分,与虚拟机一样,均属于虚拟化技术的范畴。然而,容器技术以其独特的优势,在虚拟化领域中脱颖而出。与虚拟机不同,容器能够摆脱操作系统的束缚&#xff0…

MATLAB函数,用于计算平均误差、误差最大值、标准差、均方误差、均方根误差

文章目录 源代码使用示例:计算公式1. 平均误差 (Mean Error, ME)2. 误差最大值 (Maximum Error, ME)3. 标准差 (Standard Deviation, SD)4. 均方误差 (Mean Squared Error, MSE)5. 均方根误差 (Root Mean Squared Error, RMSE) 总结 以下是一个MATLAB函数,用于计算常…

Axure设计之左右滚动组件教程(动态面板)

很多项目产品设计经常会遇到左右滚动的导航、图片展示、内容区域等,接下来我们用Axure来实现一下左右滚动的菜单导航。通过案例我们可以举一反三进行其他方式的滚动组件设计,如常见的上下滚动、翻页滚动等等。 一、效果展示: 1、点击“向左箭…

软考攻略/超详细/系统集成项目管理工程师/基础知识分享19

7.1 系统集成基础(掌握) 系统集成一般可以分为软件集成、硬件集成、网络集成、数据集成和业务应用集成等。 1、系统集成概念理解 软硬件系统集成是一种系统的思想和方法,它虽然涉及软件和硬件等技术问题,但绝不仅仅是技术问题 软硬…

数据库Redis篇

系列文章目录 第一章 C/C语言篇第二章 计算机网络篇第三章 操作系统篇第四章 数据库MySQL篇第五章 数据库Redis篇第六章 场景题/算法题第七篇 常见HR问题篇 本系列专栏:点击进入 后端开发面经 关注走一波 秋招阶段,面过很多大中小厂,积攒了…

SpringAOP技术

目录 一、概念引入 1.引入依赖 2.工具类 3.实体类 4.持久层实现类 5.业务层实现类 6.配置文件 7.测试类 8.运行 查看数据库: 9.现在如果转账过程中出现异常 AccountServiceImpl(模拟异常) 再运行: 查看数据库: 10.现在做事务…

从富文本窥探苹果的代码秘密

从富文本窥探苹果的代码秘密 背景 在我们的业务场景下,为突出诸如 “利益点”和“利率” 等特性以推动订单成交,引入了 “富文本” 这一概念。富文本具备丰富格式的文本展示与编辑功能。然而,恰是由于富文本具有 “多样式”“复杂排版” 等特…

恒创科技:如何知道一台服务器能承载多少用户?

如何知道一台服务器能承载多少用户?其实服务器承载能力并非一个单一固定的数值,而是由多种因素共同决定的动态指标,所以想知道能承载的访客量,我们要先搞清楚究竟有哪些因素会影响服务器承载访客的数量。 影响服务器承载访客的因素&#xff…

vue3配置eslint代码规划和prettier自动格式化

eslint 安装依赖:npm install -D eslint/create-config初始化:npx eslint --init初始化后会在项目中自动创建eslint.config.js文件,把以下内容复制粘粘替换 import globals from "globals"; import pluginJs from "eslint/j…

6个步骤让你快速学会甘特图的制作

在项目管理中,一个清晰、详细的进度表格和进度计划表是确保项目顺利进行的重要工具。它们不仅帮助团队成员了解各自的任务和责任,还能有效监控项目的进展,及时发现并解决问题。 制作项目进度表格和制定项目进度计划表是项目管理中的关键步骤…