Python进阶方法-Decorator装饰器

news2024/11/25 18:02:44

前言

在Python中,decorator(装饰器)是一种特殊的函数,主要用于修改或增强其他函数的功能。它可以在不修改原函数代码的情况下,通过在原函数的定义之前使用@语法糖来对其进行修饰。

Decorator装饰器的作用

Decorator的作用有以下几个方面:

1. 添加额外的功能:通过在原函数周围包裹一层装饰器函数,可以在原函数执行前后执行一些额外的代码,比如日志记录、性能分析、异常处理等。

2. 修改函数的行为:装饰器可以对原函数的输入参数进行验证、修改返回值、缓存结果等,从而改变函数的行为。

3. 分离关注点:通过使用装饰器,可以将与函数功能无关的代码(如日志记录、权限验证)与函数本身的实现分离开来,提高代码的可读性和可维护性。

4. 动态创建函数:装饰器可以在运行时动态创建函数,根据不同的条件返回不同的函数,实现动态的函数生成。

需要注意的是,装饰器只是对函数进行修饰,不会改变函数的原始定义。同时,装饰器可以堆叠使用,多个装饰器可以依次修饰同一个函数,形成装饰器链。

1、常规方法装饰功能

给方法执行时,添加额外的功能:输出方法运行的时长。

现在有2个方法:

import time  # 导入time模块  
  
  
def index():  
    time.sleep(1)  # 让程序休眠1秒钟  
    print('------------这里是index函数------------')  
  
  
def home():  
    time.sleep(1)  # 让程序休眠1秒钟  
    print('------------这里是home函数------------')

方法实现:

将目标方法包装到另外一个方法中进行统一处理

mport time  # 导入time模块  
  
  
def index():  
    time.sleep(1)  # 让程序休眠1秒钟  
    print('------------这里是index函数------------')  
  
  
def home():  
    time.sleep(1)  # 让程序休眠1秒钟  
    print('------------这里是home函数------------')  
  
  
def get_time(function_name):  
    start_time = time.time()  # 获取运行函数前的时间  
    function_name()  # 调用函数  
    end_time = time.time()  # 获取运行结束后的时间  
    print("{} 执行耗时:{}".format(function_name.__name__, end_time - start_time))  # 输出时间差  
    print("------------------------------------------------")  
  
  
get_time(index)  
get_time(home)

控制台输出结果:

------------这里是index函数------------  
index 执行耗时:1.004432201385498  
------------------------------------------------  
------------这里是home函数------------  
home 执行耗时:1.0124742984771729  
------------------------------------------------

2、初级装饰器用法

新建测试文件:

import time  # 导入time模块  
  
# 装饰对象  
def index():  
    time.sleep(1)  # 让程序休眠1秒钟  
    print('------------这里是index函数------------')  
  
  
def home():  
    time.sleep(1)  # 让程序休眠1秒钟  
    print('------------这里是home函数------------')  
  
  
# 装饰器  
def outer(function_name):  
    def inner():  
        # 获取运行函数前的时间  
        start_time = time.time()  
        # 调用名为function_name函数  
        function_name()  
        # 获取函数运行结束后的时间  
        end_time = time.time()  
        # 输出时间差  
        print("{} 执行耗时:{}".format(function_name.__name__, end_time - start_time))  
        print("------------------------------------------------")  
  
    # 返回内部函数名  
    return inner  
  
  
# 让index()变成inner()  
index = outer(index)  
index()  
  
# 让home()变成inner()  
home = outer(home)  
home()

控制台输出结果:

这个时候函数只需要调用它自己就能输出运行时间了,相当于给函数内部添加了一段代码一样。

------------这里是index函数------------  
index 执行耗时:1.0032925605773926  
------------------------------------------------  
------------这里是home函数------------  
home 执行耗时:1.0116212368011475  
------------------------------------------------

3、装饰有传参要求的方法

给装饰器加上可变长形参就可以接受任何方法需要的参数。

新建测试文件:

import time  # 导入time模块  
  
"""  
方法传参:*args, **kwargs  
"""  
  
def index(a):  # 有参数  
    time.sleep(1)  
    print('------------这里是index函数,接受的参数是:{}'.format(a))  
  
  
def home():  # 无参数  
    time.sleep(1)  
    print('------------这里是home函数------------')  
  
  
# 装饰器  
def outer(function_name):  
    def inner(*args, **kwargs):  # 添加可变长形参  
        start_time = time.time()  
        function_name(*args, **kwargs)  # 传参  
        end_time = time.time()  
        # 输出时间差  
        print("{} 执行耗时:{}".format(function_name.__name__, end_time - start_time))  
        print("------------------------------------------------")  
  
    return inner  
  
  
index = outer(index)  
index(555)  
  
home = outer(home)  
home()

控制台输出结果:

------------这里是index函数,接受的参数是:555  
index 执行耗时:1.0144884586334229  
------------------------------------------------  
------------这里是home函数------------  
home 执行耗时:1.0140643119812012  
------------------------------------------------

4、装饰有返回信息的方法

如果函数有返回值,则在装饰器内部函数加一个return返回值就行了。

新建测试文件:

import time  # 导入time模块  
  
"""  
方法传参:*args, **kwargs  
方法return  
"""  
  
  
def index(a):  # 有参数  
    time.sleep(1)  
    print('------------这里是index函数,接受的参数是:{}'.format(a))  
    return a * 2  # 有返回值  
  
  
def home():  # 无参数  
    time.sleep(1)  
    print('------------这里是home函数------------')  
  
  
def outer(function_name):  
    def inner(*args, **kwargs):  # 添加可变长形参  
        start_time = time.time()  
        # 将函数返回值赋值给res  
        res = function_name(*args, **kwargs)  # 传参  
        end_time = time.time()  
        # 输出时间差  
        print("{} 执行耗时:{}".format(function_name.__name__, end_time - start_time))  
        print("{} 方法返回信息:{}".format(function_name.__name__, res))  
          
        """返回函数的返回值"""  
        return res  
  
    return inner  
  
  
index = outer(index)  
print(index(555))  
  
home = outer(home)  
home()

控制台输出结果:

------------这里是index函数,接受的参数是:555  
index 执行耗时:1.0148656368255615  
index 方法返回信息:1110  
1110  
------------这里是home函数------------  
home 执行耗时:1.0151972770690918  
home 方法返回信息:None

5、装饰器模板&语法糖用法

编写装饰器其实有一套固定的代码 不需要做任何理解

"""编写装饰器其实有一套固定的代码 不需要做任何理解"""  
  
def decorator_func(function_name):  # func_name用于接收被装饰的对象(函数)  
    def inner(*args, **kwargs):  
        print('执行被装饰函数之前 可以做的额外操作')  
        res = function_name(*args, **kwargs)  # 执行真正的被装饰函数  
        print('执行被装饰函数之后 可以做的额外操作')  
        return res  # 返回真正函数的返回值  
  
    return inner

python给了一个方法,用@+装饰器名称放在装饰对象的上方

新建测试文件:

其中 from api_test_demo._07_log.log_demo_10 import MyLogger 代码文件参考以往的文章《Pytest测试框架中的日志记录功能详解》中第10讲。

  
"""  
装饰器模板&语法糖  
"""  
import time  
from api_test_demo._07_log.log_demo_10 import MyLogger  
  
logger = MyLogger(log_filename_default='./log_dir/decorator_demo_05_{}.log').my_logger()  
  
  
def decorator_func(function_name):  # func_name用于接收被装饰的对象(函数)  
    def inner(*args, **kwargs):  
        logger.info('-----------这里是 {} 函数------------'.format(function_name.__name__))  
        start_time = time.time()  
        res = function_name(*args, **kwargs)  # 执行真正的被装饰函数  
        end_time = time.time()  
        logger.info("{} 执行耗时:{}".format(function_name.__name__, end_time - start_time))  
        logger.info("{} 方法返回信息:{}".format(function_name.__name__, res))  
        return res  # 返回真正函数的返回值  
  
    return inner  
  
  
'''  
语法糖内部原理:  
  
使用的时候最好紧跟在被装饰对象的上方  
语法糖会自动将下面紧挨着的函数名传给@后面的函数调用  
'''  
  
  
@decorator_func  
def index(a):  # 有参数  
    time.sleep(1)  
    return a * 2  # 有返回值  
  
  
index(888)

控制台输出结果:

文件中记录结果:

6、装饰器修复功能

在Python中,使用@wraps装饰器是为了解决装饰器函数修改原函数属性的问题。

当我们使用装饰器修饰一个函数时,原函数的一些属性(如__name__、__doc__等)会被装饰器函数的属性所替代,这可能会导致一些问题,比如在调试或文档生成时无法正确显示原函数的信息。

@wraps装饰器是functools模块中的一个函数,它提供了一个简单的方式来解决这个问题。它会将装饰器函数的属性复制到原函数上,保持原函数的属性不变。

使用@wraps装饰器的语法如下:

from functools import wraps  
  
def decorator(func):  
    @wraps(func)  
    def wrapper(*args, **kwargs):  
        # 装饰器函数的逻辑  
        pass  
    return wrapper  

新建测试文件(未添加@wraps装饰器):

  
"""  
装饰器修复 wraps  
"""  
import time  
from functools import wraps  
  
from api_test_demo._07_log.log_demo_10 import MyLogger  
  
logger = MyLogger(log_filename_default='./log_dir/decorator_demo_06_{}.log').my_logger()  
  
  
def decorator_func(function_name):  # func_name用于接收被装饰的对象(函数)  
    def inner(*args, **kwargs):  
        logger.info('-----------这里是 {} 函数------------'.format(function_name.__name__))  
        start_time = time.time()  
        res = function_name(*args, **kwargs)  # 执行真正的被装饰函数  
        end_time = time.time()  
        logger.info("{} 执行耗时:{}".format(function_name.__name__, end_time - start_time))  
        logger.info("{} 方法返回信息:{}".format(function_name.__name__, res))  
        return res  # 返回真正函数的返回值  
  
    return inner  
  
  
@decorator_func  
def index(a):  # 有参数  
    time.sleep(1)  
    return a * 2  # 有返回值  
  
  
print(index)  

控制台输出结果:

可以看出,这时的index是属于inner中的方法

<function decorator_func.<locals>.inner at 0x000001467F3F70D0>

新建测试文件(添加@wraps装饰器):

"""  
装饰器修复 wraps  
"""  
import time  
from functools import wraps  
  
from api_test_demo._07_log.log_demo_10 import MyLogger  
  
logger = MyLogger(log_filename_default='./log_dir/decorator_demo_06_{}.log').my_logger()  
  
  
def decorator_func(function_name):  # func_name用于接收被装饰的对象(函数)  
    @wraps(function_name)  # 修复  
    def inner(*args, **kwargs):  
        logger.info('-----------这里是 {} 函数------------'.format(function_name.__name__))  
        start_time = time.time()  
        res = function_name(*args, **kwargs)  # 执行真正的被装饰函数  
        end_time = time.time()  
        logger.info("{} 执行耗时:{}".format(function_name.__name__, end_time - start_time))  
        logger.info("{} 方法返回信息:{}".format(function_name.__name__, res))  
        return res  # 返回真正函数的返回值  
  
    return inner  
  
  
@decorator_func  
def index(a):  # 有参数  
    time.sleep(1)  
    return a * 2  # 有返回值  
  
  
print(index)

控制台输出结果:

现在可以看到,index还是原来的index方法

<function index at 0x0000019D8FDF70D0>

7、多个装饰器

新建测试文件:

"""  
装饰多个装饰器  
"""  
import time  
from functools import wraps  
  
from api_test_demo._07_log.log_demo_10 import MyLogger  
  
logger = MyLogger(log_filename_default='./log_dir/decorator_demo_07_{}.log').my_logger()  
  
  
# 装饰器01  
def decorator_func_01(function_name):  
    @wraps(function_name)  
    def decorator(*args, **kwargs):  
        logger.info('-----------执行了:decorator_func_01------------')  
        res = function_name(*args, **kwargs)  # 执行真正的被装饰函数  
        logger.info("{} 方法返回信息:{}".format(function_name.__name__, res * 1))  
        return res  # 返回真正函数的返回值  
  
    return decorator  
  
  
# 装饰器02  
def decorator_func_02(function_name):  
    @wraps(function_name)  
    def decorator(*args, **kwargs):  
        logger.info('-----------执行了:decorator_func_02------------')  
        res = function_name(*args, **kwargs)  # 执行真正的被装饰函数  
        logger.info("{} 方法返回信息:{}".format(function_name.__name__, res * 2))  
        return res  # 返回真正函数的返回值  
  
    return decorator  
  
  
# 装饰器03  
def decorator_func_03(function_name):  
    @wraps(function_name)  
    def decorator(*args, **kwargs):  
        logger.info('-----------执行了:decorator_func_03------------')  
        res = function_name(*args, **kwargs)  # 执行真正的被装饰函数  
        logger.info("{} 方法返回信息:{}".format(function_name.__name__, res * 3))  
        return res  # 返回真正函数的返回值  
  
    return decorator  
  
  
@decorator_func_01  
@decorator_func_02  
@decorator_func_03  
def index(a):  
    time.sleep(1)  
    return a  
  
  
index(999)

控制台输出结果:

当连用多个语法糖的时候,优先执行靠近装饰对象的语法糖,并且直到最远的语法糖才会赋值给装饰对象的函数名。

也就是说,在上述代码中,优先执行@decorator_func_03,将decorator_func_03装饰到index中,但此时不会赋值给index函数名,执行到@decorator_func_01时,这时就会执行赋值语句,也就是index = decorator_func_03(index)。

8、装饰器传参功能

可以通过在装饰器外层再嵌套一个函数进行参数传递。

新建测试文件:

  
"""  
带参数的装饰器  
"""  
  
from functools import wraps  
  
from api_test_demo._07_log.log_demo_10 import MyLogger  
  
logger = MyLogger(log_filename_default='./log_dir/decorator_demo_08_{}.log').my_logger()  
  
  
# 最外层函数,可以接收参数给装饰器使用  
def decorator_func_outer(decorator_param):  
    # 装饰器  
    def decorator_func(func):  
        @wraps(func)  
        def decorator(*args, **kwargs):  
            logger.info('-----------执行了:decorator_func------------')  
  
            if 1 < decorator_param <= 99:  
                logger.info("do something with function: 1 < decorator_param <= 99")  
  
            if 99 < decorator_param <= 999:  
                logger.info("do something with function: 99 < decorator_param <= 999")  
  
            if decorator_param > 999:  
                logger.info("do something with function: decorator_param>999")  
  
            res = func(*args, **kwargs)  # 执行真正的被装饰函数  
            logger.info("{} 方法返回信息:{}".format(func.__name__, res))  
            return res * 2  # 返回真正函数的返回值  
  
        return decorator  
  
    # 这里返回装饰器的函数名  
    return decorator_func  
  
  
@decorator_func_outer(decorator_param=6)  
def index(a):  
    return a  
  
  
index(a=888)

控制台输出结果:

总结

Decorator是一种强大的Python语言特性,可以通过装饰器函数对其他函数进行修饰,从而实现功能增强、行为修改、关注点分离等效果。同时,使用@wraps装饰器可以保持原函数的属性不变,提高代码的可维护性和可读性。

---------------------------END---------------------------

题外话

在这里插入图片描述

感兴趣的小伙伴,赠送全套Python学习资料,包含面试题、简历资料等具体看下方。

👉CSDN大礼包🎁:全网最全《Python学习资料》免费赠送🆓!(安全链接,放心点击)

一、Python所有方向的学习路线

Python所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照下面的知识点去找对应的学习资源,保证自己学得较为全面。

img
img

二、Python必备开发工具

工具都帮大家整理好了,安装就可直接上手!img

三、最新Python学习笔记

当我学到一定基础,有自己的理解能力的时候,会去阅读一些前辈整理的书籍或者手写的笔记资料,这些笔记详细记载了他们对一些技术点的理解,这些理解是比较独到,可以学到不一样的思路。

img

四、Python视频合集

观看全面零基础学习视频,看视频学习是最快捷也是最有效果的方式,跟着视频中老师的思路,从基础到深入,还是很容易入门的。

img

五、实战案例

纸上得来终觉浅,要学会跟着视频一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。

img

六、面试宝典

在这里插入图片描述

在这里插入图片描述

简历模板在这里插入图片描述

👉CSDN大礼包🎁:全网最全《Python学习资料》免费赠送🆓!(安全链接,放心点击)

若有侵权,请联系删除

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

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

相关文章

端口信息收集

一、服务端口介绍 在渗透测试中对服务端口的收集非常重要&#xff0c;通过扫描服务开放的端口可判断对应开启的服务&#xff0c;通过所提供的这些服务的已知漏洞就可进行攻击。知名端口&#xff08;0-1023&#xff09;固定分配给某些服务的&#xff0c;动态端口&#xff08;10…

(高频面试1)Redis缓存穿透、缓存击穿、缓存雪崩

目录 一&#xff1a;缓存数据 1.1 应用场景 1.2&#xff1a;缓存数据出现的问题 1.2.1 缓存穿透 1.2.2 解决办法 1.2.3 缓存击穿 1.2.4 解决办法 1.2.5 缓存雪崩 1.2.6 解决办法 一&#xff1a;缓存数据 1.1 应用场景 数据库查询结果缓存是一种常见的缓存应用场景&a…

ansible 使用roles简单部署LAMP平台

目录 一、了解roles目录 二、基于构建LAMP平台创建roles目录 1、在192.168.115.148创建目录 2、书写php的测试页面 3、编写httpd角色的main.yml文件 4、编写mysql角色的main.yml文件 6、编写lamp的playbook 7、启动剧本 8、访问 一、了解roles目录 在Ansible中&#…

MultipartFile是什么

Multipart是一种file的类型 在我们进行文件上传时所发出的请求&#xff0c;我们页面对请求格式有明确的要求: 1.post提交表单方式 2.编码格式enctype必须是muitipart/form-data&#xff0c;这种格式适合传输数据量大的二进制数据文件 3.类型必须是file类 流程举例&#xf…

开源药店商城系统源码比较:哪个适合你的药品电商业务

在构建药品电商业务时&#xff0c;选择适合的药店商城系统源码是至关重要的决策之一。开源药店商城系统源码提供了快速入门的机会&#xff0c;但在选择之前&#xff0c;您需要仔细考虑您的需求、技术要求和可扩展性。本文将比较几个流行的开源药店商城系统源码&#xff0c;以帮…

追溯网络安全本源,原生安全范式框架v1.0外滩大会正式发布

9月8日&#xff0c;2023外滩大会网络安全分论坛在上海举行。论坛由蚂蚁集团和《信息安全研究》杂志社联合主办&#xff0c;以“开启原生安全范式&#xff0c;护航网络空间安全”为主题。会上蚂蚁集团与浙江大学网络空间安全学院重磅首发了一项引领性网络安全成果 “原生安全范式…

什么?你还不会打包给运维?!那快来看看

目录 一、首先是JAVA打包 我们只需要用maven打包即可&#xff0c;生成文件为xxxx.jar 二、Vue打包 而打包是运行 npm run build 只要把 dist 给运维就行了 PS&#xff1a;如果是线上运行&#xff0c;那你要注意 env这个文件&#xff01;&#xff01;&#xff01; ​编辑…

进程间通信(IPC)的方法:命名管道

使用管道时&#xff0c;一个进程的输出可成为另外一个进程的输入。 命名管道(Named pipe或FIFO)是一种类似于管道的特殊文件&#xff0c;但在文件系统上有一个名称&#xff0c;它允许以先进先出(FIFO, first in, first out)的方式存储有限数量的数据。它的使用类似于消息…

(STM32H5系列)STM32H573RIT6、STM32H573RIV6、STM32H573ZIT6嵌入式微控制器基于Cortex®-M33内核

一、应用 工业&#xff08;PLC、工业电机控制、泵和压缩机&#xff09; 智能家居&#xff08;空调、冰箱、冰柜、中央警报系统、洗衣机&#xff09; 个人电子产品&#xff08;键盘、智能手机、物联网标签、跟踪设备&#xff09; 智能城市&#xff08;工业通信、照明控制、数字…

2023秋冬系列丨追求本真的自然纯粹之美

2023年08月&#xff0c;上海&#xff0c;ZESH泽尚&#xff0c;中国轻奢皮具品牌宣布推出2023全新秋冬系列包袋&#xff0c;以“自然之道&#xff0c;纯粹之美”为主题重新定义东方美学。品牌建立之初就坚持贯彻东方美学设计与精湛制作工艺融合的理念。此次秋冬系列从中式禅宗学…

EagleSDR USB HAT FT600

给EagleSDR做了个USB 3.0的子卡&#xff0c;采用FT600方案&#xff0c;实物如下&#xff1a; 用FT600DataStreamerDemoApp测试&#xff0c;速度如下&#xff1a; 由于FT600是16bit的接口&#xff0c;如果用FT601的32bit接口&#xff0c;性能应该还会有大幅提升。 测试代码很简…

通过Idea或命令将本地项目上传至git

通过Idea或命令将本地项目上传至git 一、Git创建仓库 1、登录Gitee账号&#xff0c;点击新建 2、填写如下相关信息&#xff0c;点击创建 3、在此处可以复制项目链接 二、Idea配置和解绑git&#xff0c;提交项目 1、idea打开项目&#xff0c;操作如下 2、在弹框里选择…

【监控系统】Promethus整合Alertmanager监控告警邮件通知

【监控系统】Promethus整合Alertmanager监控告警邮件通知 Alertmanager是一种开源软件&#xff0c;用于管理和报警监视警报。它与Prometheus紧密集成&#xff0c;后者是一种流行的开源监视和警报系统。Alertmanager从多个源接收警报和通知&#xff0c;并根据一组配置规则来决定…

Linux中的用户和用户组

su和exit命令 su命令就是用于账户切换的系统命令&#xff0c;其来源英文单词:Switch User 语法: su [-] [用户名] - 符号是可选的&#xff0c;表示是否在切换用户后加载环境变量 &#xff08;建议带上&#xff09;参数:用户名&#xff0c;表示要切换的用户&#xff0c;用户名也…

怎么提高自己当众讲话的能力?

当众讲话是一项重要的沟通技能&#xff0c;它可以帮助你在各种场合中表达自己的观点、影响他人&#xff0c;并建立自信。虽然对很多人来说&#xff0c;当众讲话可能是一项挑战&#xff0c;但通过一些实践和技巧&#xff0c;你可以提高自己的当众讲话能力。下面是一些方法&#…

Java开发之Redis核心内容【面试篇 完结版】

文章目录 前言一、redis使用场景1. 知识分布2. 缓存穿透① 问题引入② 举例说明③ 解决方案④ 实战面试 3. 缓存击穿① 问题引入② 举例说明③ 解决方案④ 实战面试 4. 缓存雪崩① 问题引入② 举例说明③ 解决方案④ 实战面试 5. 缓存-双写一致性① 问题引入② 举例说明③ 解决…

第3章_瑞萨MCU零基础入门系列教程之开发环境搭建与体验

本教程基于韦东山百问网出的 DShanMCU-RA6M5开发板 进行编写&#xff0c;需要的同学可以在这里获取&#xff1a; https://item.taobao.com/item.htm?id728461040949 配套资料获取&#xff1a;https://renesas-docs.100ask.net 瑞萨MCU零基础入门系列教程汇总&#xff1a; ht…

滚珠螺杆螺母的加工方法

螺母就是螺帽&#xff0c;与螺栓或螺杆拧在一起用来起紧固作用的零件&#xff0c;螺母的用途十分广泛&#xff0c;那么它的加工方法&#xff0c;你了解吗&#xff1f;接下来&#xff0c;我们一起来看一下。 1、车削&#xff1a;最早使用的加工方法&#xff0c;加工螺母的工艺路…

Openlayers 教程 - feature(图形要素)点击和双击事件

Openlayers 教程 - feature&#xff08;图形要素&#xff09;点击和双击事件 核心代码完整代码&#xff1a;在线示例 地图中图形要素&#xff08;Feature&#xff09;的点击事件非常常用&#xff0c;曾经通过给整个地图绑定点击事件&#xff0c;然后判断图形要素&#xff08;Fe…