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

Python装饰器进阶:深入理解与最佳实践

1、什么是装饰器

https://docs.python.org/zh-cn/3.7/glossary.html#term-decorator
官网介绍的很简单,返回值为另一个函数的函数,通常使用 @wrapper 语法形式来进行函数变换。装饰器就是闭包的应用,是用来**装饰(修改或增强)**函数、方法、类。

import timedef runtime(function):"""统计运行时间"""def wrapper():"""装饰函数"""start_time = time.time()function()print(f"runtime is {time.time() - start_time}")return wrapperdef fetch_http_data():print('开始请求网络数据')time.sleep(1)print('数据请求完成')@runtime
def parse_response_data():"""解析数据"""print('开始解析数据')time.sleep(0.5)print('数据解析完成')# 把函数当作参数传到另一个函数中执行,但是这种会改变调用方式
decorator = runtime(fetch_http_data)
decorator()# 使用语法糖,不会改变调用方式
parse_response_data()# 被装饰的函数,查看函数名的时候 变成了wrapper,所以装饰器会改变原函数的一些属性
print(parse_response_data.__name__) # wrapper

2、保留装饰器中函数的元数据

parse_response_data.**name** #wrapper
parse_response_data.**doc** # “”“装饰函数”“”
被装饰的函数,查看函数名的时候 变成了wrapper,函数的文档注释也改变了, 所以装饰器会改变原函数的一些属性,如何保留原函数的属性呢?

from functools import wraps# @wrap,它会帮助保留原函数的元信息
# @wraps 有一个重要特征是它能让你通过属性 __wrapped__ 直接访问被包装函数
# parse_response_data.__wrapped__() 可以调用原函数def runtime(function):"""统计运行时间"""@wraps(function)def wrapper():start_time = time.time()function()print(f"runtime is {time.time() - start_time}")return wrapper@runtime
def parse_response_data():print('开始解析数据')time.sleep(0.5)print('数据解析完成')print(parse_response_data.__name__)  # parse_response_data
print(parse_response_data.__doc__)  # 解析数据
# 通过__wrapped__属性调用原函数 
parse_response_data.__wrapped__()

3、带参数的装饰器

装饰器可以带参数,这样可以使装饰器更加灵活和通用,根据不同的情况对被装饰的函数应用不同的行为

def retry(max_retries=3):def decorator(func):def wrapper(*args, **kwargs):for retry_count in range(max_retries):try:result = func(*args, **kwargs)return resultexcept Exception as e:if retry_count < max_retries:print(f"Retrying {func.__name__} (attempt {retry_count + 1}/{max_retries})...")time.sleep(2)else:raise ereturn wrapperreturn decorator@retry(max_retries=2)
def potentially_failing_function():import randomif random.random() < 0.7:print("Function succeeded!")else:raise Exception("Function failed.")potentially_failing_function()

retry 装饰器接受一个 max_retries 参数,用于指定最大的重试次数。decorator 函数接受被装饰的函数 func,并定义了 wrapper 包装函数,该包装函数尝试执行 func,如果遇到异常则进行重试,最多尝试 max_retries 次。
然后,potentially_failing_function 函数应用了带参数的装饰器,允许在最多 2 次重试之后终止或成功执行。

4、 带可选参数的装饰器

import timedef timing_decorator(func=None, message="Execution time"):def decorator(wrapped_func):def wrapper(*args, **kwargs):start_time = time.time()result = wrapped_func(*args, **kwargs)end_time = time.time()execution_time = end_time - start_timeprint(f"{message}: {execution_time} seconds")return resultreturn wrapperif func is None:return decoratorelse:return decorator(func)@timing_decorator(message="Function 1 took")
def function1():time.sleep(2)print("Function 1 completed.")@timing_decorator
def function2():time.sleep(1)print("Function 2 completed.")function1()
function2()

5、 用类实现装饰器

除了使用函数实现装饰器外,类也可以实现装饰器,

import time
import functoolsclass runtime:def __init__(self, func):self.func = func# 保留被装饰函数的元数据functools.update_wrapper(self, func)def __get__(self, instance, owner):if instance is None:return self# 创建一个可调用的对象,将 instance作为self的参数传递进去return functools.partial(self, instance)def __call__(self, *args, **kwargs):start_time = time.time()result = self.func(*args, **kwargs)end_time = time.time()execution_time = end_time - start_timeprint(f"{self.func.__name__} took {execution_time} seconds")return result@runtime
def some_function():time.sleep(2)print("Function completed.")class Animal:@runtimedef walk(self, road):time.sleep(2)print(f"{self.__class__} walk {road}")some_function()
a = Animal()
a.walk('马路')

6、 类中的方法实现装饰器

# 类中的普通方法实现一个装饰器
class DecoratedMeta:def runtime(self, function):"""统计运行时间"""@functools.wraps(function)def wrapper(*args, **kwargs):"""装饰函数"""start_time = time.time()result = function(*args, **kwargs)print(f"runtime is {time.time() - start_time}")return resultreturn wrapper@classmethoddef runtime_cls(cls, function):@functools.wraps(function)def wrapper(*args, **kwargs):print('使用类方法的装饰器')return function(*args, **kwargs)return wrapperd = DecoratedMeta()@d.runtime  # 使用装饰器
def add(x, y):return x + y@d.runtime_cls
def sub(x, y):return x - yresult = add(2, 3)
print(result)
result = sub(4, 5)
print(result)

7、 给类加上装饰器

1、给类中的方法加装饰器

import timedef runtime(function):"""统计运行时间"""def wrapper(*args, **kwargs):"""装饰函数"""start_time = time.time()function(*args, **kwargs)print(f"runtime is {time.time() - start_time}")return wrapperclass Animal:@runtimedef walk(self):time.sleep(2)print(f"{self.__class__} walk")a = Animal()
a.walk()

2、给类加装饰器,扩充类的功能

# 定义一个装饰器函数
def log_decorator(cls):# 保存原始类的构造函数original_init = cls.__init__# 定义一个新的构造函数,扩充功能def new_init(self, *args, **kwargs):# 首先调用原始构造函数original_init(self, *args, **kwargs)# 扩展功能:在构造对象时打印信息print(f"创建 {self.__class__.__name__}")# 将新的构造函数替换原始构造函数cls.__init__ = new_initreturn cls# 使用装饰器扩充类的功能
@log_decorator
class MyClass:def __init__(self, x, y):self.x = xself.y = ydef add(self):return self.x * self.y# 创建类的实例
obj = MyClass(3, 4)# 扩充功能生效,构造对象时打印信息
result = obj.add()
print(result)

8、装饰器的叠加

import time
from functools import wrapsdef runtime(function):"""统计运行时间"""@wraps(function)def wrapper(*args, **kwargs):"""装饰函数"""start_time = time.time()result = function(*args, **kwargs)print(f"runtime is {time.time() - start_time}")return resultreturn wrapperdef printlog(function):"""函数运行日志:param function::return:"""@wraps(function)def wrapper(*args, **kwargs):print(f"{function.__name__} start")result = function(*args, **kwargs)print(f"{function.__name__} over")return resultreturn wrapper@printlog
@runtime
def add(x, y):time.sleep(0.5)return x + ydef sub(x, y):return x - y# 调用过程
# a = runtime(add)
# b = printlog(a)
# b(1,3)add(1, 3)a = runtime(sub)
b = printlog(a)
res = b(1, 3)
print(res)

9、 内置的装饰器

@classmethod
把一个方法封装成类方法。

# python的redis第三方库中,使用url连接redis时定义的from_url是一个类方法。
class Redis(object):""""""@classmethoddef from_url(cls, url, db=None, **kwargs):""""""connection_pool = ConnectionPool.from_url(url, db=db, **kwargs)return cls(connection_pool=connection_pool)# 调用类方法
redis_ints = Redis.from_url('redis://user:password@127.0.0.1:6379/0')

@staticmethod
将方法转换为静态方法。

import mathclass CrawlSite:# 使用静态方法计算页数,与实例无关,工具方法@staticmethoddef get_page(total, offsize):"""计算要爬取的页数"""return math.ceil(total / offsize)

@property
会将方法转化为一个具有相同名称的只读属性的 “getter”,特征属性对象具有 getter, setter 以及 deleter 方法,它们可用作装饰器来创建该特征属性的副本,并将相应的访问函数设为所装饰的函数

class Animal(object):def __init__(self, eat):self.__eat = eat# 只有@property时属性不能赋值操作@propertydef eat(self):return self.__eat@eat.setterdef eat(self, value):# 设置属性值,同时可以做校验、计算等if not isinstance(value, str):raise TypeError('Expected a string')self.__eat = value@eat.deleterdef eat(self):del self.__eata = Animal('rot')
print(a.eat)
a.eat = 'cao'
print(a.eat)

@functools.wraps
保留被装饰的函数元信息,用于在定义包装器函数时发起调用 update_wrapper() 作为函数装饰器
@functools.lru_cache
一个为函数提供缓存功能的装饰器,如果调用相同,则直接返回缓存中的值,不需要重新计算。用以节约高开销或I/O函数的调用时间。
如果 maxsize 设置为 None ,LRU功能将被禁用且缓存数量无上限。由于使用了字典存储缓存,所以该函数的固定参数和关键字参数必须是可哈希的。

@lru_cache(maxsize=100)  
def fibonacci(n):  if n < 2:  return n  return fibonacci(n-1) + fibonacci(n-2)  print(fibonacci(10))  # 输出 55  
print(fibonacci(10))  # 输出 55,不会重新计算

@functools.singledispatch
实现只有第一个参数可接受不同类型的函数

from functools import singledispatch@singledispatch
def calculate_area(argument):raise NotImplementedError('Unsupported operand type')@calculate_area.register(int)
def _(argument):return argument * argument@calculate_area.register(str)
def _(argument):return int(argument) * int(argument)print(calculate_area(5))  # 输出 25
print(calculate_area('6'))  # 输出 36

@contextlib.contextmanager
它可以定义一个支持 with 语句上下文的工厂函数, 而不需要创建一个类或区 enter() 与 exit() 方法。

import contextmanager
import timedef adds():for i in range(3):print(i)time.sleep(1)@contextlib.contextmanager
def timing_context(func):start_time = time.time()try:func()yield 'runtime'  # 进入上下文 yield后面的值,就会赋在 with语句的as 后面finally:end_time = time.time()elapsed_time = end_time - start_timeprint(f"Elapsed time: {elapsed_time} seconds")# 使用上下文管理器来测量代码块的执行时间
with timing_context(adds) as msg:# 模拟耗时操作print(msg)

10、 自定义常用的装饰器

重试机制

import functools
import timedef retries(max_retries=3, delay=1):def decorator(func):@functools.wraps(func)def wrapper(*args, **kwargs):retry_count = 0while retry_count < max_retries:try:return func(*args, **kwargs)except Exception as e:print(f"Error: {func.__name__} failed with {e}. Retrying in {delay} seconds...")retry_count += 1time.sleep(delay)raise Exception(f"Error: {func.__name__} failed after {max_retries} retries.")return wrapperreturn decorator@retries()
def some_function():# Some code that might fail.print('----------------')@retries(max_retries=5, delay=3)
def another_function():# Some code that might fail.print('=============')raisesome_function()another_function()

超时判断

import timeimport functools
from concurrent import futurespool = futures.ThreadPoolExecutor(1)def runtime(seconds):def decorator(func):@functools.wraps(func)def wrapper(*args, **kw):future = pool.submit(func, *args, **kw)return future.result(timeout=seconds)return wrapperreturn decorator@runtime(3)
def request_http():time.sleep(2)return 111res = request_http()
print(res)

相关文章:

Python装饰器进阶:深入理解与最佳实践

1、什么是装饰器 https://docs.python.org/zh-cn/3.7/glossary.html#term-decorator 官网介绍的很简单&#xff0c;返回值为另一个函数的函数&#xff0c;通常使用 wrapper 语法形式来进行函数变换。装饰器就是闭包的应用&#xff0c;是用来**装饰&#xff08;修改或增强&…...

数据库数据恢复—Oracle数据库报错ORA-01110错误的数据恢复案例

Oracle数据库故障&#xff1a; 北京某公司一台运行oracle数据库的服务器&#xff0c;机房意外断电导致该服务器重启&#xff0c;重启后发现oracle数据库报错。该Oracle数据库没有备份。 Oracle数据库数据恢复过程&#xff1a; 1、北亚企安数据恢复工程师检查该oracle数据库的数…...

如何通过adb控制安卓手机wifi

一、准备工作 1、先用USB数据线 将手机和电脑连接在一起 1&#xff09; 数据线连接手机和电脑&#xff0c;选择“传输文件”的连接方式&#xff1b; 2&#xff09; 在手机上&#xff0c;打开“开发者选项”、“USB调试”&#xff1b; 2、在电脑上安装adb工具&#xff0c;参考…...

VR全景应用广泛体现在哪里?有何优势?

VR全景作为一种新型营销方式&#xff0c;正在逐渐走进人们的视线&#xff0c;它区别于以往单一角度的照片和视频&#xff0c;VR全景制作显得更加直观、更加真实、更加生动。VR全景通过VR技术将所拍摄的图片变成720度可观看的场景模式&#xff0c;把产品的特色以及魅力整体呈现展…...

【深度学习】Python使用指定gpu运行代码

命令行指定显卡GPU运行python脚本 在大型机构分配的服务器集群中&#xff0c;需要使用GPU的程序默认都会在第一张卡上进行&#xff0c;如果第一张卡倍别人占用或者显存不够的情况下&#xff0c;程序就会报错说没有显存容量&#xff0c;所以能够合理地利用GPU资源能帮助你更快更…...

二叉树的遍历

树森林二叉树先序遍历先序遍历先序遍历后序遍历中序遍历中序遍历 1.前序遍历 leetcode题目链接 1.1 递归 前序遍历递归方式 class Solution { public:vector<int> preorderTraversal(TreeNode* root) {vector<int> res;if(root){res.push_back(root->val);ve…...

分布式限流:Redis

目录 1:如何实现分布式限流 2:限流的几种类别 2.1:固定窗口限流 2.2:滑动窗口限流 2.3:漏桶限流 2.4:令牌桶限流 3:实现分布式限流:Redis 3.1:引入Redisson的依赖包 3.2:初始化Redisson 3.3:创建Redisson的限流类 1:如何实现分布式限流 1:把统计用户的使用频率等这些…...

python excel接口自动化测试框架

前言 前些天写了pytestyamlallure接口自动化测试框架这篇文章。 今天采用Excel继续写一个接口自动化测试框架。 设计流程图 这张图是我的excel接口测试框架的一些设计思路。 首先读取excel文件&#xff0c;得到测试信息&#xff0c;然后通过封装的requests方法&#xff0c…...

Java开发面试--MongoDB专区

1、你是否了解 MongoDB&#xff1f; 答&#xff1a; 是的&#xff0c;我了解 MongoDB。MongoDB是一个流行的NoSQL数据库&#xff0c;它以文档的形式存储数据&#xff0c;具有高度的灵活性和可扩展性。我熟悉MongoDB的基本概念和特性&#xff0c;例如集合&#xff08;Collectio…...

当『后设学习』碰上『工程学思维』

只要我成为一个废物&#xff0c;就没人能够利用我&#xff01; 雷猴啊&#xff0c;我是一只临期程序猿。打过几年工&#xff0c;写过几行代码。但今天我不想聊代码&#xff0c;我们聊聊学习这件事。 技术年年更新&#xff0c;尤其是前端框架&#xff0c;很多时候觉得学习速度都…...

一表谈现实、系统、流程、报表与BI

序号主题描述1系统与现实1.1管理系统个体应用决定因素管理能力、软件工程与管理环境的综合结果1.2信息系统与现实业务的关系现实世界是以业务利弊为抓手&#xff1b;信息系统是以业务流程为抓手&#xff1b;信息系统只是对现实业务部分的数字化应用总结&#xff1a;现实业务是以…...

数据结构顺序栈例题一

内容仅供个人复习 #include<iostream> #define MAXSIZE 100using namespace std;typedef struct {int *base;int *top;int stackSize;}SqStack;void Initstack( SqStack &s) // 初始化栈 {s.base new int[MAXSIZE];s.top s.base;s.stackSize MAXSIZE; }void Pus…...

大模型在百度智能问答、搜索中的应用

本文主要介绍了智能问答技术在百度搜索中的应用。包括机器问答的发展历程、生成式问答、百度搜索智能问答应用。欢迎大家加入百度搜索团队&#xff0c;共同探索智能问答技术的发展方向&#xff0c;文末有简历投递方式。 01 什么是机器问答 机器问答&#xff0c;就是让计算机…...

ARPG----C++学习记录01日志和调试

多人射击有点难&#xff0c;发现这个更加基础&#xff0c;先学习这个 显示日志 可以在代码中插入这样一行来打印日志&#xff0c;蓝图里的printstring会在屏幕和日志里都显示。可以使用%f&#xff0c;d等来获取后边的输入值。对于打映字符串变量&#xff0c;传入需要* UE_LOG…...

3302. 表达式求值, 栈的应用

3302. 表达式求值 - AcWing题库 给定一个表达式&#xff0c;其中运算符仅包含 ,-,*,/&#xff08;加 减 乘 整除&#xff09;&#xff0c;可能包含括号&#xff0c;请你求出表达式的最终值。 注意&#xff1a; 数据保证给定的表达式合法。题目保证符号 - 只作为减号出现&…...

论文写作框架示例:论软件系统建模方法及其应用

标题 前言题目要求写作框架(1)摘要(300~330字)(2)正文(2000~2500字,2200字左右为宜)(3)收尾(200字左右)前言 本章内容参考了51cto的薛老师的《软考论文高分特训与范文10篇》的内容,是帮助初学者打开写作思路的工具,而不是必须要遵循的模式。建议软考人多读多看…...

Godot 官方2D C#重构(4):TileMap进阶使用

文章目录 前言完成内容项目节点结构TileMap设置图片资源备选图片添加物理碰撞添加y轴遮罩判断Y Sort Enable是干什么的&#xff1f; 脚本代码 前言 Godot 官方 教程 Godot 2d 官方案例C#重构 专栏 Godot 2d 重构 github地址 完成内容 项目节点结构 TileMap设置 图片资源 备选图…...

Ubuntu系统编译调试QGIS源码保姆级教程

在之前的文章中&#xff0c;我详细介绍了怎么在Windows下编译QGIS源码&#xff0c;也得到了不错的反馈。但是不足的是Windows下只能编译QGIS的Release模式和RelWithDebInfo模式&#xff0c;想要分析源码&#xff0c;“断点调试”肯定是少不了的&#xff0c;但是这两种模式虽然也…...

电源控制系统架构(PCSA)之系统控制处理器

安全之安全(security)博客目录导读 目录 一、系统控制处理器 1、服务 2、可信操作 一、系统控制处理器 SCP是一种基于处理器的能力&#xff0c;为提供电源管理功能和服务提供了一个灵活和可扩展的平台。 在移动系统中&#xff0c;SCP处理器一般是Cortex-M微控制器&#xff…...

深度学习之轻量级神经网络在TWS蓝牙音频处理器上的部署

加我微信hezkz17进数字音频系统研究开发交流答疑群(课题组) 深度学习之轻量级神经网络在TWS蓝牙音频处理器上的部署 深度学习之轻量级神经网络在TWS蓝牙音频处理器上的部署 深度学习之轻量级神经网络在TWS蓝牙音频处理器上的部署 项目一 科大讯飞经验 在Matlab平台上实现广义…...

CentOS下的分布式内存计算Spark环境部署

一、Spark 核心架构与应用场景 1.1 分布式计算引擎的核心优势 Spark 是基于内存的分布式计算框架&#xff0c;相比 MapReduce 具有以下核心优势&#xff1a; 内存计算&#xff1a;数据可常驻内存&#xff0c;迭代计算性能提升 10-100 倍&#xff08;文档段落&#xff1a;3-79…...

【AI学习】三、AI算法中的向量

在人工智能&#xff08;AI&#xff09;算法中&#xff0c;向量&#xff08;Vector&#xff09;是一种将现实世界中的数据&#xff08;如图像、文本、音频等&#xff09;转化为计算机可处理的数值型特征表示的工具。它是连接人类认知&#xff08;如语义、视觉特征&#xff09;与…...

04-初识css

一、css样式引入 1.1.内部样式 <div style"width: 100px;"></div>1.2.外部样式 1.2.1.外部样式1 <style>.aa {width: 100px;} </style> <div class"aa"></div>1.2.2.外部样式2 <!-- rel内表面引入的是style样…...

Map相关知识

数据结构 二叉树 二叉树&#xff0c;顾名思义&#xff0c;每个节点最多有两个“叉”&#xff0c;也就是两个子节点&#xff0c;分别是左子 节点和右子节点。不过&#xff0c;二叉树并不要求每个节点都有两个子节点&#xff0c;有的节点只 有左子节点&#xff0c;有的节点只有…...

重启Eureka集群中的节点,对已经注册的服务有什么影响

先看答案&#xff0c;如果正确地操作&#xff0c;重启Eureka集群中的节点&#xff0c;对已经注册的服务影响非常小&#xff0c;甚至可以做到无感知。 但如果操作不当&#xff0c;可能会引发短暂的服务发现问题。 下面我们从Eureka的核心工作原理来详细分析这个问题。 Eureka的…...

iOS性能调优实战:借助克魔(KeyMob)与常用工具深度洞察App瓶颈

在日常iOS开发过程中&#xff0c;性能问题往往是最令人头疼的一类Bug。尤其是在App上线前的压测阶段或是处理用户反馈的高发期&#xff0c;开发者往往需要面对卡顿、崩溃、能耗异常、日志混乱等一系列问题。这些问题表面上看似偶发&#xff0c;但背后往往隐藏着系统资源调度不当…...

LLMs 系列实操科普(1)

写在前面&#xff1a; 本期内容我们继续 Andrej Karpathy 的《How I use LLMs》讲座内容&#xff0c;原视频时长 ~130 分钟&#xff0c;以实操演示主流的一些 LLMs 的使用&#xff0c;由于涉及到实操&#xff0c;实际上并不适合以文字整理&#xff0c;但还是决定尽量整理一份笔…...

scikit-learn机器学习

# 同时添加如下代码, 这样每次环境(kernel)启动的时候只要运行下方代码即可: # Also add the following code, # so that every time the environment (kernel) starts, # just run the following code: import sys sys.path.append(/home/aistudio/external-libraries)机…...

【Android】Android 开发 ADB 常用指令

查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...

解决:Android studio 编译后报错\app\src\main\cpp\CMakeLists.txt‘ to exist

现象&#xff1a; android studio报错&#xff1a; [CXX1409] D:\GitLab\xxxxx\app.cxx\Debug\3f3w4y1i\arm64-v8a\android_gradle_build.json : expected buildFiles file ‘D:\GitLab\xxxxx\app\src\main\cpp\CMakeLists.txt’ to exist 解决&#xff1a; 不要动CMakeLists.…...