python高阶知识之函数装饰器详解

先看一个示例

定义一个函数,传入数字,经过for循环后写入txt文件。


def writenum(num:int):""":param num: 传入数字:return:"""for i in range(num):with open('1.txt','a') as e:e.write(str(i))

当执行函数后会在相同目录下生成一个1.txt的文件,但是我想确定写入文件消耗的时间,并且不打算修改上面的函数,于是编写另一个函数:函数内嵌套了一个函数,并返回该函数

import time
def print_exec_time(func):""":param func: 传入一个函数:return: 返回闭包函数"""def exec(num):""":param num: 装饰器内的函数传入数字num:return:"""starttime = time.time()func(num)endtime = time.time()print(f'花费了:{endtime-starttime}s')return exec

函数 print_exec_time执行过程:

1)函数 print_exec_time的入参是一个函数func,当执行时会返回exec函数

2)exec函数入参是num,执行exec函数时执行到func(num),其实执行的就是原函数writenum。

而多增加一个嵌套函数的作用是在执行func(num)这一步骤前后增加时间打印并最终计算消耗的时间。

我们将函数writenum作为参数传给函数print_exec_time,执行print_exec_time(writenum)(500)结果如下:

花费了:0.016657114028930664s

并且在本地也同样生成了1.txt文件,那如果换成如下方式呢:

将函数print_exec_time作为writenum的装饰器,增加@print_exec_time

@print_exec_time
def writenum(num:int):for i in range(num):with open('1.txt','a') as e:e.write(str(i))

此时我们直接执行函数writenum(500),结果如下:

花费了:0.01768326759338379s

结论:函数装饰后,执行writenum(500)等同于执行print_exec_time(writenum)(500),达到被装饰的目的。

什么是函数装饰器?

装饰器(decorator)是函数一种高阶用法,定义时需要满足几个条件:

1 函数内定义嵌套函数并返回该函数(这种叫函数闭包)

2 将函数作为参数传入

这样看来实现也不是很复杂,平常我们也会经常用到装饰器函数,比如类中的@staticmethod,@classmethod,Flask框架中的@app.route()等。

使用functools.wraps保护被装饰的函数本身的属性不被修改

函数被装饰器函数装饰后,会改变原函数的一些属性之比如__name__,__doc__

函数被装饰前我们执行如下语句

print(f'writenum 函数的name: {writenum.__name__}')
print(f'writenum 函数的__doc__属性: {writenum.__doc__}')

结果如下:

writenum 函数的name: writenum
writenum 函数的__doc__属性:
:param num: 传入数字
:return:

函数装饰后执行相同语句,结果如下:

writenum 函数的name: exec
writenum 函数的__doc__属性:
:param num: 装饰器内的函数传入数字num
:return:

结果发现writenum函数的属性已经被改为了装饰器闭包函数的属性

解决方法:使用functools.wraps功能,在闭包函数上使用@wraps(func)

from functools import wraps
import time
def print_exec_time(func):""":param func: 传入一个函数:return: 返回闭包函数"""@wraps(func)def exec(num):""":param num: 装饰器内的函数传入数字num:return:"""starttime = time.time()func(num)endtime = time.time()print(f'花费了:{endtime-starttime}s')return exec

此时再次执行如下语句,结果如下:保持了原函数本身的属性

writenum 函数的name: writenum
writenum 函数的__doc__属性:
:param num: 传入数字
:return:

装饰器支持闭包函数带参数*args,**kwargs

之前文章介绍函数的参数有位置参数和关键字参数,多个参数我们可以写成func(*args,**kwargs), *args表示代表传入多个位置参数, **kwargs代表传入多个关键字参数。

我们将装饰器中原先参数num,替换为*args,**kwargs,这样就会支持很多不同参数的函数。

def print_exec_time(func):""":param func: 传入一个函数:return: 返回闭包函数"""@wraps(func)def exec(*args,**kwargs):""":param num: 装饰器内的函数传入数字num:return:"""starttime = time.time()func(*args,**kwargs)endtime = time.time()print(f'花费了:{endtime-starttime}s')return exec@print_exec_time
def writenum(*args,**kwargs):""":param args:  位置参数:param kwargs  关键字参数:return:"""print(args)print(kwargs)for i in (args):with open('1.txt','a') as e:e.write(str(i))for i in kwargs.items():with open('1.txt','a') as e:e.write(str(i))

同样改造下函数writenum,然后执行writenum(1,2,3,name='lili',age=12)也是没问题的。

装饰器函数带参数

上面示例中的装饰器函数的参数传入的是原函数本身,那如果装饰器函数本身像普通函数需要传参数,那怎么处理呢?  其实很简单,在装饰器函数中再嵌套一个闭包函数。

示例如下:

from functools import wraps
import time
def print_exec_time(param):""":param func: 传入一个函数:return: 返回闭包函数"""print(f'传入参数{param}')def param_exec(func):@wraps(func)def exec(num):""":param num: 装饰器内的函数传入数字num:return:"""starttime = time.time()func(num)endtime = time.time()print(f'花费了:{endtime -starttime}s')return execreturn param_exec

基于上述例子,函数 print_exec_time带参数,在内部再增加一个闭包函数 param_exec,而这个函数的入参变成了函数func,跟上面讲到的示例就是一样了。

那我们装饰函数的时候就可以带上参数了:@print_exec_time('传入数字100')

@print_exec_time('传入数字100')
def add(num):i  = 0for j in range(1,num+1):time.sleep(0.01)i+=jprint(f'result:{i}')

执行add(100),结果如下:

传入参数传入数字100
result:5050
花费了:1.0658702850341797s

多个装饰器函数如何执行?

定义装饰器函数func1和func2,originfunc函数被这两个装饰器函数装饰。


def func1(func):print(f'执行函数 func1')@wraps(func)def exec():print(f'执行函数 func1的嵌套函数')func()print(f'结束执行函数 func1的嵌套函数')return execdef func2(func):print(f'执行函数 func2')@wraps(func)def exec():print(f'执行函数 func2的嵌套函数')func()print(f'结束执行函数 func2的嵌套函数')return exec@func2
@func1
def originfunc():print(f'执行了原函数 originfunc')

执行originfunc(),结果如下:

执执行函数 func1
执行函数 func2
执行函数 func2的嵌套函数
执行函数 func1的嵌套函数
执行了原函数 originfunc
结束执行函数 func1的嵌套函数
结束执行函数 func2的嵌套函数

结果分析如下:

1 函数先依次从下到上执行装饰器函数中闭包函数以上的函数内容

2 然后依次从上到下,执行闭包函数中原函数以上的内容

3 执行原函数的内容

4 最后从下到上执行原函数以下的内容

因为经过装饰器装饰后,originfunc这个函数相当于变成了originfunc=func2(func1(originfunc))

执行originfunc()相当于执行func2(func1(originfunc)()。

类装饰器的实现

在类中通过使用 __init__和 __call__方法来实现,__init__接收要被装饰的函数,__call__实现具体的装饰内容。
类装饰器作用是用来装饰函数。

比如以上示例,我们创建一个类class来实现,代码如下

class TESTDEC(object):# 通过初始化方法,传入被装饰的函数def __init__(self, func):self.__func = func# 重写 __call__ 方法来实现具体装饰内容def __call__(self, num):print('执行类的装饰器')starttime = time.time()self.__func(num)endtime = time.time()print(f'花费了:{endtime - starttime}s')

  使用类装饰器,并执行add(100),结果是一样的。

@TESTDEC
def add(num):i  = 0for j in range(1,num+1):time.sleep(0.01)i+=jprint(f'result:{i}')
共勉: 东汉·班固《汉书·枚乘传》:“泰山之管穿石,单极之绠断干。水非石之钻,索非木之锯,渐靡使之然也。”

-----指水滴不断地滴,可以滴穿石头;

-----比喻坚持不懈,集细微的力量也能成就难能的功劳。

----感谢读者的阅读和学习,谢谢大家。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/52817.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java面试题·区别题·JavaSE部分

系列文章目录 总章 Java区别题 文章目录 系列文章目录前言private/默认/protected/public权限修饰符的区别&和&&区别和联系,I和II区别和联系if和switch的不同之处和equals的区别和联系数组做形参和可变参数做形参联系和区别接口和抽象类的异同之处面向…

嵌入式day36

数据库 专业存储数据、大量数据 数组、链表、变量---->内存:程序运行结束、掉电数据丢失 文件---->硬盘:程序运行结束、掉电数据不丢失 数据库---->硬盘 数据库文件与普通文件区别: 1.普通文件对数据管理(增删改查…

Linux入门攻坚——30、sudo、vsftpd

su:Switch User,即切换用户 su [-l user] -c ‘COMMAND’ 如:su -l root -c ‘COMMAND’ 如果没有指定-l user,则默认是root sudo:可以让某个用户不需要拥有管理员的密码,而可以执行管理员的权限。 需…

基于RS232的VGA显示

前言 基于ROM的VGA显示缺点:需要将图片转化为mif文件,使用的RAM是FPGA内部RAM模拟出来的,占用资源大切换显示图片需要重新转化,对ROM进行写入,使用极不方便,因此这里采用RS232进行VGA显示。 正文 一、基于…

代码随想录Day 28|题目:122.买卖股票的最佳时机Ⅱ、55.跳跃游戏、45.跳跃游戏Ⅱ、1005.K次取反后最大化的数组和

提示:DDU,供自己复习使用。欢迎大家前来讨论~ 文章目录 题目题目一:122.买卖股票的最佳时机 II贪心算法:动态规划 题目二:55.跳跃游戏解题思路: 题目三: 45.跳跃游戏 II解题思路方法一方法二 题…

鸿蒙开发入门day15-焦点事件

(创作不易,感谢有你,你的支持,就是我前行的最大动力,如果看完对你有帮助,还请三连支持一波哇ヾ(@^∇^@)ノ) 目录 焦点事件 基础概念与规范 基础概念 走焦规范 走焦算法 获焦/失…

16. TreeMap和HashMap的区别是什么?在什么场景下应该使用TreeMap?

TreeMap 和 HashMap 都是 Java 中常用的 Map 接口的实现类,它们在存储键值对时有不同的实现方式和特性。了解它们的区别和适用场景可以帮助你在实际开发中选择合适的集合类型。 TreeMap 和 HashMap 的主要区别 1. 底层实现 HashMap: HashMap 基于哈希表&…

电话客服软件的深度解析:功能、优势与应用场景

一、引言 1.1 电话客服的重要性 在当今竞争激烈的市场环境中,优质的客户服务已成为企业脱颖而出的关键因素之一。电话客服作为最传统也是最直接的沟通方式,始终占据着不可替代的地位。它不仅能够快速响应客户需求,解决客户问题,…

【逐行注释】MATLAB下的UKF(无迹卡尔曼滤波),带丰富的中文注释,可直接复制到MATLAB上运行,无需下载

文章目录 程序组成部分完整代码运行结果主要模块解读:运动模型绘图部分误差统计特性输出程序组成部分 由模型初始化、运动模型、UKF主体部分、绘图代码和输出部分组成: 完整代码 将下列代码复制粘贴到MATLAB里面,即可运行: % 三维状态量的UKF例程 % 作者联系方式:微信…

机器视觉系统

1、机器视觉应用场景 1、识别定位 2、缺陷检测 3、ocr 4、测量类的 2、视觉系统 镜头 相机 采集卡 计算机 显示器 3、开发流程 1、需求分析 2、可行性分析 3、方案设计 4、概要设计 5、详细设计 6、调试 7、测试 8、交付 9、维护 4、光学系统 1、望远 2、放大 3、显微 4、摄影…

安全面试常见问题任意文件下载

《网安面试指南》http://mp.weixin.qq.com/s?__bizMzkwNjY1Mzc0Nw&mid2247484339&idx1&sn356300f169de74e7a778b04bfbbbd0ab&chksmc0e47aeff793f3f9a5f7abcfa57695e8944e52bca2de2c7a3eb1aecb3c1e6b9cb6abe509d51f&scene21#wechat_redirect 1.1 任意文件下…

学习记录——day39 C++ Class this指针

一、封装 Class 1、C 中的类 Class C中的类,是由C中的结构体演化而来的,只需要将struct改成关键字class,就定义了一个类 C中类和结构体的区别: 1)默认的权限不同,结构体中默认权限为public,类…

培训第三十九天(了解docker-compose,docker-compose编排容器,配置harbor服务)

一、回顾 1、拉取私有仓库镜像 # 配置dockerdocker pull 10.0.0.10:5000/centosnginx:v0 2、容器网络类型 brideg(net) default# docker启动之后会生成新的虚拟网卡,网卡的名称docker0# 网段默认是172.17.0.1# 所有的容器都桥接docker0,通过桥接共享网…

LRN正则化是什么?

LRN正则化,全称为Local Response Normalization(局部响应归一化),是一种在深度学习,特别是在卷积神经网络(CNN)中常用的正则化技术。该技术旨在通过模拟生物视觉系统中的侧抑制现象,…

OpenLayers3, 设置地图背景

文章目录 一、前言二、代码实现三、总结 一、前言 本文基于OpenLayers3&#xff0c;实现地图加入背景图的功能。 二、代码实现 <!DOCTYPE html> <html xmlns"http://www.w3.org/1999/xhtml"> <head><meta http-equiv"Content-Type"…

QT学习ubuntu qt + desktop

环境搭建 ubuntu 安装QT 遇到kit 选择不了 通过sudo apt-get install qt5-default去安装SDK的时候报错&#xff1a; Package qt5-default is not available, but is referred to by another package. This may mean that the package is missing, has been obsoleted, or is …

Linux——nginx 负载均衡

常规的web服务器一般提供对于静态资源的访问&#xff0c;比如说&#xff1a;图片、web样式 网站提供的大部分交互功能都需要web编程语言的支持&#xff0c;而web服务对于程序的调用&#xff0c;不管编译型语言还是解释型语言&#xff0c;web服务同将对于应用程序的调用递交给通…

RedisMessageListenerContainer容器初始化

RedisMessageListenerContainer是Spring Data Redis提供的一个容器类&#xff0c;为Redis监听器提供异步处理能力&#xff0c;处理低级别消息、转换Redis的消息通道&#xff0c;它通常与MessageListenerAdapter和自定义的消息监听器一起使用。 一、RedisMessageListenerContain…

【机器学习】网络安全如何利用(行为分析)来确定可能表明内部威胁、APT 或零日攻击的可疑或异常事件。

网络安全如何利用&#xff08;行为分析&#xff09;来确定可能表明内部威胁、APT 或零日攻击的可疑或异常事件。 1. 行为分析的基本概念 2. 检测内部威胁 3. 检测高级持续性威胁 (APT) 4. 检测零日攻击 5. 实施行为分析的步骤 6. 行为分析的优势与挑战 7. 总结 &#x1…

(十七)Flink 容错机制

目录 分布式快照 Checkpoint Checkpoint 模式 Checkpoint 配置 非对齐 Checkpointing 状态存储 Savepoint 分配算子 ID Savepoint 操作 Checkpoint 与 Savepoint 区别 作业重启与故障恢复策略 重启策略 恢复策略 对于不间断 24 小时运行的程序来说,容错至关重要。…