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版本开始,支持更…...
第19节 Node.js Express 框架
Express 是一个为Node.js设计的web开发框架,它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用,和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...
测试微信模版消息推送
进入“开发接口管理”--“公众平台测试账号”,无需申请公众账号、可在测试账号中体验并测试微信公众平台所有高级接口。 获取access_token: 自定义模版消息: 关注测试号:扫二维码关注测试号。 发送模版消息: import requests da…...
超短脉冲激光自聚焦效应
前言与目录 强激光引起自聚焦效应机理 超短脉冲激光在脆性材料内部加工时引起的自聚焦效应,这是一种非线性光学现象,主要涉及光学克尔效应和材料的非线性光学特性。 自聚焦效应可以产生局部的强光场,对材料产生非线性响应,可能…...
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以? 在 Golang 的面试中,map 类型的使用是一个常见的考点,其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...
SpringTask-03.入门案例
一.入门案例 启动类: package com.sky;import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCach…...
【C++从零实现Json-Rpc框架】第六弹 —— 服务端模块划分
一、项目背景回顾 前五弹完成了Json-Rpc协议解析、请求处理、客户端调用等基础模块搭建。 本弹重点聚焦于服务端的模块划分与架构设计,提升代码结构的可维护性与扩展性。 二、服务端模块设计目标 高内聚低耦合:各模块职责清晰,便于独立开发…...
Fabric V2.5 通用溯源系统——增加图片上传与下载功能
fabric-trace项目在发布一年后,部署量已突破1000次,为支持更多场景,现新增支持图片信息上链,本文对图片上传、下载功能代码进行梳理,包含智能合约、后端、前端部分。 一、智能合约修改 为了增加图片信息上链溯源,需要对底层数据结构进行修改,在此对智能合约中的农产品数…...
20个超级好用的 CSS 动画库
分享 20 个最佳 CSS 动画库。 它们中的大多数将生成纯 CSS 代码,而不需要任何外部库。 1.Animate.css 一个开箱即用型的跨浏览器动画库,可供你在项目中使用。 2.Magic Animations CSS3 一组简单的动画,可以包含在你的网页或应用项目中。 3.An…...
RabbitMQ入门4.1.0版本(基于java、SpringBoot操作)
RabbitMQ 一、RabbitMQ概述 RabbitMQ RabbitMQ最初由LShift和CohesiveFT于2007年开发,后来由Pivotal Software Inc.(现为VMware子公司)接管。RabbitMQ 是一个开源的消息代理和队列服务器,用 Erlang 语言编写。广泛应用于各种分布…...
力扣热题100 k个一组反转链表题解
题目: 代码: func reverseKGroup(head *ListNode, k int) *ListNode {cur : headfor i : 0; i < k; i {if cur nil {return head}cur cur.Next}newHead : reverse(head, cur)head.Next reverseKGroup(cur, k)return newHead }func reverse(start, end *ListNode) *ListN…...
