Python进阶方法-Decorator装饰器

前言

在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学习资料》免费赠送🆓!(安全链接,放心点击)

若有侵权,请联系删除

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值