Python 装饰器使用详解
文章目录
- 0. 引言
- 1. 什么是装饰器?
- 2. 装饰器的基本语法
- 3. 装饰器的工作原理
- 4. 常见装饰器应用场景
- 4.1. 日志记录
- 4.2. 权限校验
- 4.3. 缓存
- 5. 多重装饰器的执行顺序
- 6. 装饰器的高级用法
- 6.1. 带参数的装饰器
- 6.2. 使用 `functools.wraps`
- 6.3. 类装饰器
- 7. 图示说明
- 7.1. 单一装饰器的执行流程
- 2. 多重装饰器的执行流程
- 3. 带参数装饰器的执行流程
- 总结
- 8 参考资料
0. 引言
Python装饰器(Decorator) 不仅可以让你的代码更加简洁、可读,还能有效地实现功能的复用和扩展。本文将带你深入了解Python装饰器的概念、原理及其应用。
1. 什么是装饰器?
装饰器是一种高阶函数,它接受一个函数作为参数,并返回一个新的函数。通过装饰器,我们可以在不修改原函数代码的前提下,动态地为其添加额外的功能。
简单来说,装饰器就是函数的包装器,它可以在函数执行前后添加一些操作。
2. 装饰器的基本语法
在Python中,装饰器通常使用 @
符号来应用于函数或类。下面是一个简单的装饰器示例:
def my_decorator(func):def wrapper():print("函数执行前的操作")func()print("函数执行后的操作")return wrapper@my_decorator
def say_hello():print("Hello!")say_hello()
输出:
函数执行前的操作
Hello!
函数执行后的操作
在这个例子中:
my_decorator
是一个装饰器,它接受一个函数func
作为参数。wrapper
是一个内部函数,它在调用func
前后添加了额外的打印操作。@my_decorator
将say_hello
函数传递给装饰器,并将返回的wrapper
函数重新赋值给say_hello
。
因此,当我们调用 say_hello()
时,实际执行的是 wrapper()
函数。
3. 装饰器的工作原理
装饰器的核心在于闭包和高阶函数。让我们通过一个更详细的示例来理解装饰器的工作机制。
def decorator(func):print("装饰器被调用")def wrapper(*args, **kwargs):print("在函数执行前")result = func(*args, **kwargs)print("在函数执行后")return resultreturn wrapper@decorator
def add(a, b):print(f"执行加法: {a} + {b}")return a + bresult = add(3, 5)
print(f"结果是: {result}")
输出:
装饰器被调用
在函数执行前
执行加法: 3 + 5
在函数执行后
结果是: 8
工作流程图示:
+------------------+
| @decorator |
| 装饰器被调用 |
| add 函数被传入 |
+---------+--------+|v
+---------+--------+
| 返回 wrapper 函数 |
+---------+--------+|v
+---------+--------+
| 调用 add(3, 5) | ---> 实际上调用的是 wrapper(3, 5)
+---------+--------+|v
+---------+--------+
| 打印 "在函数执行前"|
| 调用原始 add |
| 打印 "执行加法:3 +5"|
| 打印 "在函数执行后"|
+---------+--------+|v
+---------+--------+
| 返回结果 8 |
+------------------+
解释:
- 当Python解释器遇到
@decorator
时,它会先调用decorator(add)
。 decorator
函数在执行时首先打印“装饰器被调用”。decorator
返回了wrapper
函数,因此add
函数被替换为wrapper
。- 当调用
add(3, 5)
时,实际上调用的是wrapper(3, 5)
,它在执行func(3, 5)
(即原始的add
函数)前后添加了打印操作。
4. 常见装饰器应用场景
装饰器在实际开发中有着广泛的应用,以下是几个常见的使用场景:
4.1. 日志记录
记录函数的调用信息、参数、返回值等,有助于调试和监控。
def log_decorator(func):def wrapper(*args, **kwargs):print(f"调用函数 {func.__name__},参数: {args}, {kwargs}")result = func(*args, **kwargs)print(f"函数 {func.__name__} 返回: {result}")return resultreturn wrapper@log_decorator
def multiply(a, b):return a * bmultiply(4, 5)
输出:
调用函数 multiply,参数: (4, 5), {}
函数 multiply 返回: 20
4.2. 权限校验
在函数执行前进行权限检查,确保用户有权限执行该操作。
def requires_permission(permission):def decorator(func):def wrapper(*args, **kwargs):if not user_has_permission(permission):raise PermissionError("没有权限执行此操作")return func(*args, **kwargs)return wrapperreturn decorator@requires_permission('admin')
def delete_user(user_id):print(f"删除用户 {user_id}")
4.3. 缓存
缓存函数的计算结果,避免重复计算,提高性能。
def cache_decorator(func):cache = {}def wrapper(*args):if args in cache:print("使用缓存")return cache[args]result = func(*args)cache[args] = resultreturn resultreturn wrapper@cache_decorator
def fibonacci(n):if n <= 1:return nreturn fibonacci(n-1) + fibonacci(n-2)print(fibonacci(5))
5. 多重装饰器的执行顺序
当一个函数被多个装饰器装饰时,装饰器的执行顺序可能会让人感到困惑。下面通过一个示例来说明多重装饰器的执行顺序。
def decorator_a(func):print("装饰器 A 被调用")def wrapper(*args, **kwargs):print("装饰器 A 在函数执行前")result = func(*args, **kwargs)print("装饰器 A 在函数执行后")return resultreturn wrapperdef decorator_b(func):print("装饰器 B 被调用")def wrapper(*args, **kwargs):print("装饰器 B 在函数执行前")result = func(*args, **kwargs)print("装饰器 B 在函数执行后")return resultreturn wrapper@decorator_a
@decorator_b
def greet(name):print(f"Hello, {name}!")greet("Alice")
输出:
装饰器 A 被调用
装饰器 B 被调用
装饰器 A 在函数执行前
装饰器 B 在函数执行前
Hello, Alice!
装饰器 B 在函数执行后
装饰器 A 在函数执行后
执行顺序图示:
装饰器应用阶段:
+-----------------+ +-----------------+
| decorator_a | | decorator_b |
| 调用 decorator_a | | 调用 decorator_b |
+--------+--------+ +--------+--------+| |v v
+--------+--------+ +--------+--------+
| 返回 wrapper_a | | 返回 wrapper_b |
+--------+--------+ +--------+--------+| |+----------> greet <-------+指向 wrapper_a函数调用阶段:
+-----------------+
| 调用 greet("Alice") |
+--------+--------+|v
+--------+--------+
| wrapper_a |
| 打印 "装饰器 A 在函数执行前" |
| 调用 wrapper_b |
+--------+--------+|v
+--------+--------+
| wrapper_b |
| 打印 "装饰器 B 在函数执行前" |
| 调用 greet ("Hello, Alice!") |
| 打印 "装饰器 B 在函数执行后" |
+--------+--------+|v
+--------+--------+
| wrapper_a |
| 打印 "装饰器 A 在函数执行后" |
+-----------------+
解释:
-
装饰器的应用顺序是自下而上:
- 首先,
greet
函数被decorator_b
装饰,生成wrapper_b
。 - 然后,
wrapper_b
被decorator_a
装饰,生成wrapper_a
。 - 最终,
greet
指向wrapper_a
。
- 首先,
-
函数调用的执行顺序是自上而下:
- 调用
greet("Alice")
实际上调用的是wrapper_a("Alice")
。 wrapper_a
打印“装饰器 A 在函数执行前”,然后调用wrapper_b("Alice")
。wrapper_b
打印“装饰器 B 在函数执行前”,然后调用原始的greet("Alice")
。- 原始的
greet
打印“Hello, Alice!”。 - 然后,
wrapper_b
打印“装饰器 B 在函数执行后”。 - 最后,
wrapper_a
打印“装饰器 A 在函数执行后”。
- 调用
6. 装饰器的高级用法
6.1. 带参数的装饰器
有时候,装饰器本身需要接受参数,这时需要使用三层嵌套函数。
def repeat(num_times):def decorator(func):def wrapper(*args, **kwargs):for _ in range(num_times):result = func(*args, **kwargs)return resultreturn wrapperreturn decorator@repeat(num_times=3)
def say(message):print(message)say("Hello!")
输出:
Hello!
Hello!
Hello!
6.2. 使用 functools.wraps
在装饰器中,使用 functools.wraps
可以保留原函数的元数据,如函数名、文档字符串等。
import functoolsdef my_decorator(func):@functools.wraps(func)def wrapper(*args, **kwargs):print("调用前")return func(*args, **kwargs)return wrapper@my_decorator
def example():"""这是一个示例函数"""print("示例函数执行")print(example.__name__) # 输出: example
print(example.__doc__) # 输出: 这是一个示例函数
6.3. 类装饰器
装饰器不仅可以用于函数,也可以用于类。
def class_decorator(cls):class WrappedClass:def __init__(self, *args, **kwargs):self.wrapped_instance = cls(*args, **kwargs)def __getattr__(self, attr):return getattr(self.wrapped_instance, attr)def new_method(self):print("这是新添加的方法")return WrappedClass@class_decorator
class MyClass:def method(self):print("原始方法")obj = MyClass()
obj.method()
obj.new_method()
输出:
原始方法
这是新添加的方法
7. 图示说明
为了更直观地理解装饰器的工作原理及其执行顺序,下面通过几张示意图来辅助说明。
7.1. 单一装饰器的执行流程
示意图:
装饰器应用阶段:
+-----------------+
| Original Func | (被装饰的函数)
+--------+--------+|v
+--------+--------+
| Decorator | (装饰器函数)
| 返回 Wrapper |
+--------+--------+|v
+--------+--------+
| Wrapper Func | (包装后的函数)
+--------+--------+函数调用阶段:
+-----------------+
| 调用 Wrapper |
+--------+--------+|v
+--------+--------+
| 执行装饰器前操作 |
+--------+--------+|v
+--------+--------+
| 执行原始函数 |
+--------+--------+|v
+--------+--------+
| 执行装饰器后操作 |
+-----------------+
解释:
- 装饰器应用阶段:原始函数通过装饰器包装,生成一个新的包装函数。
- 函数调用阶段:调用包装函数时,先执行装饰器前的操作,再执行原始函数,最后执行装饰器后的操作。
2. 多重装饰器的执行流程
示意图:
装饰器应用阶段:
+-----------------+ +-----------------+
| Original Func | | Decorator B |
+--------+--------+ +--------+--------+| |v v
+--------+--------+ +--------+--------+
| Decorator A | | 返回 Wrapper B |
| 返回 Wrapper A | +-----------------+
+--------+--------+|v
+--------+--------+
| Wrapper A |
+-----------------+函数调用阶段:
+-----------------+
| 调用 Wrapper A |
+--------+--------+|v
+--------+--------+
| 执行 Decorator A 前操作 |
+--------+--------+|v
+--------+--------+
| 调用 Wrapper B |
+--------+--------+|v
+--------+--------+
| 执行 Decorator B 前操作 |
+--------+--------+|v
+--------+--------+
| 执行原始函数 |
+--------+--------+|v
+--------+--------+
| 执行 Decorator B 后操作 |
+--------+--------+|v
+--------+--------+
| 执行 Decorator A 后操作 |
+-----------------+
解释:
-
装饰器应用阶段:
- 原始函数首先被
Decorator B
装饰,生成Wrapper B
。 - 然后,
Wrapper B
被Decorator A
装饰,生成Wrapper A
。 - 最终,函数指向
Wrapper A
。
- 原始函数首先被
-
函数调用阶段:
- 调用
Wrapper A
,执行Decorator A
的前置操作。 Wrapper A
调用Wrapper B
,执行Decorator B
的前置操作。Wrapper B
调用原始函数。- 执行
Decorator B
的后置操作。 - 执行
Decorator A
的后置操作。
- 调用
3. 带参数装饰器的执行流程
示意图:
装饰器应用阶段:
+-----------------+
| repeat(num_times=3) |
+--------+--------+|v
+--------+--------+
| Decorator |
| 返回 Wrapper |
+--------+--------+|v
+--------+--------+
| 原始函数 |
+--------+--------+函数调用阶段:
+-----------------+
| 调用 Wrapper |
+--------+--------+|v
+--------+--------+
| 重复调用原始函数 |
| 3 次 |
+-----------------+
解释:
- 装饰器应用阶段:带参数的装饰器
repeat
接受参数num_times=3
,返回装饰器函数decorator
,然后decorator
返回wrapper
函数。 - 函数调用阶段:调用
wrapper
时,根据num_times
的值,重复调用原始函数 3 次。
总结
装饰器通过函数包装器的方式,允许开发者在不修改原函数代码的前提下,为其添加额外的功能。
8 参考资料
- Python 官方文档 - 装饰器
- Python Decorators 101
- Functools 模块
相关文章:

Python 装饰器使用详解
文章目录 0. 引言1. 什么是装饰器?2. 装饰器的基本语法3. 装饰器的工作原理4. 常见装饰器应用场景4.1. 日志记录4.2. 权限校验4.3. 缓存 5. 多重装饰器的执行顺序6. 装饰器的高级用法6.1. 带参数的装饰器6.2. 使用 functools.wraps6.3. 类装饰器 7. 图示说明7.1. 单…...

Vue使用qrcodejs2-fix生成网页二维码
安装qrcodejs2-fix npm install qrcodejs2-fix核心代码 在指定父view中生成一个二维码通过id找到父布局 //通过id找到父布局let codeView document.getElementById("qrcode")new QRCode(codeView, {text: "测试",width: 128,height: 128,colorDark: #00…...

兼容多个AI应用接口,支持用户自定义切换AI接口
项目背景 2023年ChatGPT横空出世,给IT行业造成了巨大的反响。我第一次发现这个ChatGPT有着如此神奇的功能(智能对话,知识问答,代码生成,逻辑推理等),我感到非常吃惊!经过一番学习和…...

[docker]入门
本文章主要讲述的是,docker基本实现原理,docker概念的解释,docker的使用场景以及docker打包与部署的应用。 文章中docker所运行的系统:CentOS Linux release 7.9.2009 (Core) 目录 docker是什么,什么时候需要去使用 …...

《让手机秒变超级电脑!ToDesk云电脑、易腾云、青椒云移动端评测》
前言 科技发展到如今2024年,可以说每一年都在发生翻天覆地的变化。而云上这个词时常都被大家提起,从个人设备连接到云端在如今在也不是梦了。而云电脑这个市场近年来迅速发展,无需购买和维护额外的硬件就可以体验到电脑端顶配的性能和体验&am…...

Nginx处理带有分号“;“的路径
一、背景 安全渗透测试发现springboot 未授权访问的actuator和Swagger-ui 信息泄露的漏洞,需要规避。解决方案中较简单的就是通过Nginx将相关的接口转发到403页面。 在配置的过程当中,遇到了带有…;的路径:http://{ip:port}/{path}/…;/actu…...

Spring Boot框架下的心理教育辅导系统开发
1绪 论 1.1研究背景 随着计算机和网络技术的不断发展,计算机网络已经逐渐深入人们的生活,网络已经能够覆盖我们生活的每一个角落,给用户的网上交流和学习提供了巨大的方便。 当今社会处在一个高速发展的信息时代,计算机网络的发展…...

PyTorch 图像分割模型教程
PyTorch 图像分割模型教程 在图像分割任务中,目标是将图像的每个像素归类为某一类,以分割出特定的物体。PyTorch 提供了非常灵活的工具,可以用于构建和训练图像分割模型。我们将使用 PyTorch 的经典网络架构,如 UNet 和 DeepLabV…...

物联网——USART协议
接口 串口通信 硬件电路 电平标准 串口参数、时序 USART USART主要框图 TXE: 判断发送寄存器是否为空 RXNE: 判断接收寄存器是否非空 RTS为输出信号,用于表示MCU串口是否准备好接收数据,若输出信号为低电平,则说明MCU串口可以接收数据&#…...

前端框架对比与选择:如何在现代Web开发中做出最佳决策
随着互联网技术的迅速发展,前端开发在现代Web应用开发中扮演了至关重要的角色。对于开发者来说,选择合适的前端框架不仅能够提高开发效率,还能确保项目的可维护性和可扩展性。目前市面上有多种主流的前端框架和库,每一种都有其独特…...

【浅水模型MATLAB】尝试复刻SCI论文中的溃坝流算例
【浅水模型MATLAB】尝试复刻SCI论文中的溃坝流算例 前言问题描述控制方程及数值方法浅水方程及其数值计算方法边界条件的实现 代码框架与关键代码模拟结果 更新于2024年9月17日 前言 这篇博客算是学习浅水方程,并利用MATLAB复刻Liang (2004)1中溃坝流算例的一个记录…...

探索云计算:IT行业的未来趋势
探索云计算:IT行业的未来趋势 在当今快速发展的科技世界,云计算已成为IT行业的核心趋势之一。无论是大企业还是初创公司,越来越多的组织正在转向云计算,以实现更高效的运营和更快的创新。在这篇博文中,我们将探讨云计算…...

[PICO VR眼镜]眼动追踪串流Unity开发与使用方法,眼动追踪打包报错问题解决(Eye Tracking/手势跟踪)
前言 最近在做一个工作需要用到PICO4 Enterprise VR头盔里的眼动追踪功能,但是遇到了如下问题: 在Unity里面没法串流调试眼动追踪功能,根本获取不到Device,只能将整个场景build成APK,安装到头盔里,才能在…...

一周热门|比GPT-4强100倍,OpenAI有望年底发布GPT-Next;1个GPU,1分钟,16K图像
大模型周报将从【企业动态】【技术前瞻】【政策法规】【专家观点】四部分,带你快速跟进大模型行业热门动态。 01 企业动态 Ilya 新公司 SSI 官宣融资 10 亿美元 据路透社报道,由 OpenAI 联合创始人、前首席科学家 Ilya Sutskever 在 2 个多月前共同创…...

软考流水线计算
某计算机系统输入/输出采用双缓冲工作方式,其工作过程如下图所示,假设磁盘块与缓冲区大小相同,每个盘块读入缓冲区的时间T为10μs,由缓冲区送至用户区的时间M为6μs,系统对每个磁盘块数据的处理时间C为2μs。若用户需要…...

1份可以派上用场丢失数据恢复的应用程序列表
无论如何,丢失您的宝贵数据是可怕的。您的 Android 或 iOS 设备可能由于事故、硬件损坏、存储卡问题等而丢失了数据。这就是为什么我们编制了一份可以派上用场以恢复丢失数据的应用程序列表。 如果您四处走动,您大多会随身携带手机或其他移动设备。这些…...

MySQL Workbench 超详细安装教程(一步一图解,保姆级安装)
前言: MySQL Workbench 是一款强大的数据库设计和管理工具,它提供了图形化界面,使得数据库的设计、管理、查询等操作变得更加直观和便捷。本文将详细介绍如何在 Windows 系统上安装 MySQL Workbench。相信读者看这篇文章前一定安装了MySQL数…...

深度学习常见面试题及答案(16~20)
算法学习、4对1辅导、论文辅导或核心期刊以及其他学习资源可以通过公众号滴滴我 文章目录 16. 简述深度学习中的批量归一化(Batch Normalization)的目的和工作原理。一、批量归一化的目的1. 加速训练收敛:2. 提高模型泛化能力:3. …...

Packet Tracer - IPv4 ACL 的实施挑战(完美解析)
目标 在路由器上配置命名的标准ACL。 在路由器上配置命名的扩展ACL。 在路由器上配置扩展ACL来满足特定的 通信需求。 配置ACL来控制对网络设备终端线路的 访问。 在适当的路由器接口上,在适当的方向上 配置ACL。…...

Langchain-chatchat源码部署及测试实验
一年多前接触到Langchain-chatchat的0.2版本,对0.2版本进行了本地部署和大量更新,但0.2版本对最新的大模型支持不够好,部署框架支持也不好且不太稳定,特别是多模态大模型,因此本次主要介绍0.3版本的源码部署,希望对大家有所帮助。Langchain-chatchat从0.3版本开始,支持更…...

【Linux】线程(第十六篇)
目录 线程 1.线程基本概述: 2.线程类型: 3.线程间的共享资源与非共享资源 4.线程原语 1.线程创建函数 2.获取当前线程id的函数 3.回收线程资源 4.将线程设置为分离态 5.结束线程 6.退出线程 线程 1.线程基本概述: 是操作系统能够…...

2024华为杯研赛E题保姆级教程思路分析
E题题目:高速公路应急车道紧急启用模型 今年的E题设计到图像/视频处理,实际上,E题的难度相对来说较低,大家不用畏惧视频的处理,被这个吓到。实际上,这个不难,解决了视频的处理问题,…...

国内可以使用的ChatGPT服务【9月持续更新】
首先基础知识还是要介绍得~ 一、模型知识: GPT-4o:最新的版本模型,支持视觉等多模态,OpenAI 文档中已经更新了 GPT-4o 的介绍:128k 上下文,训练截止 2023 年 10 月(作为对比,GPT-4…...

Linux环境Docker安装Mongodb
Linux环境Docker安装Mongodb 环境要求拉取指定版本镜像创建映射目录(相当于数据存放于容器外,容器被删除不会影响数据)启动容器 进入mongo命令行为指定db创建新用户查看mongodb的容器id进入命令行查看所有db切换db为指定db创建新用户使用新账…...

PyTorch 池化层详解
在深度学习中,池化层(Pooling Layer)是卷积神经网络(CNN)中的关键组成部分。池化层的主要功能是对特征图进行降维和减少计算量,同时增强模型的鲁棒性。本文将详细介绍池化层的作用、种类、实现方法…...

Intel架构的基本知识
1.字节序 CPU的字节序分为LittleEndian和BigEndian。 所谓Endian,就是多字节数据在内存中的排列方式。 例如,假设有一个整数0x11223344: LittleEndian的排列方式是,从内存的低地址开始,依次存放 0x44 0x33 0x22 0x11; BigEndian的排列方式是,从内存的低地址开始,依…...

Element Plus 中Input输入框
通过鼠标或键盘输入字符 input为受控组件,他总会显示Vue绑定值,正常情况下,input的输入事件会正常被响应,他的处理程序应该更新组件的绑定值(或使用v-model)。否则,输入框的值将不会改变 不支…...

大模型中常见 loss 函数
loss 函数 首先,Loss 是允许不降到 0 的,模型计算的 loss 最终结果可以接近 0。 可以成为 loss 函数的条件## 常用 loss 以下函数调用基于 Pytorch,头文件导入: import torch.nn as nn 均方差(MSE) nn.…...

(十六)Ubuntu 20.04 下搭建PX4+MATLAB 仿真环境(HITL)
在文章(十五)Ubuntu 20.04 下搭建PX4MATLAB 仿真环境我们学习了如何配置仿真环境,在本节,主要进行HITL的仿真环境搭建。 根据(十五)Ubuntu 20.04 下搭建PX4MATLAB 仿真环境完成配置到如下界面:…...

Matlab simulink建模与仿真 第十七章(补充离散库和补充数学库)
参考视频:simulink1.1simulink简介_哔哩哔哩_bilibili 一、补充离散库和补充数学库中的模块概览 1、补充离散库 注:每个版本的补充离散库不一定相同,也不是每个版本的库都有如上所有模块。 2、补充数学库 二、离散直接传递函数Ⅱ模块 1、…...