• Python进阶——装饰器


    目录

    装饰器的定义

    被装饰的函数有参数

    装饰器函数自身有参数


    装饰器的定义

    装饰器 ,英文称之为decorator

    我们开发Python代码,会经常碰到装饰器。

    Python中装饰器通常用来装饰函数、或者类的方法。

    通常被装饰后的函数, 会在原有的函数基础上,增加一点功能。

    比如 前面我们在学习到类里面的静态方法,就是使用了staticmethod 这个装饰器,被装饰的方法 就增加了一层含义,表示这个方法是个静态方法。

    通常装饰器本身是也一个函数。 那么装饰器是怎么装饰另外的函数的呢?

    假设你进入一个公司,领导要求你在老代码的基础上继续开发。

    假设代码里面有好多函数,都是返回时间的,比如像下面这样。

    1. import time
    2. def getXXXTime():
    3. print()
    4. return time.strftime('%Y_%m_%d %H:%M:%S',time.localtime())

    如果我们需要在所有这样的函数 返回字符串前面 都加上开头: 当地时间

    这时候,我们完全可以 不去修改原来的函数 , 而是 使用装饰器 ,像这样

    1. import time
    2. # 定义一个装饰器函数
    3. def sayLocal(func):
    4. def wrapper():
    5. curTime = func()
    6. return f'当地时间: {curTime}'
    7. return wrapper
    8. def getXXXTime():
    9. return time.strftime('%Y_%m_%d %H:%M:%S',time.localtime())
    10. # 装饰 getXXXTime
    11. getXXXTime = sayLocal(getXXXTime)
    12. print (getXXXTime())

    执行下面这行代码之前 ,

    getXXXTime = sayLocal(getXXXTime)
    

    getXXXTime 这个名字 对应一个函数对象 内容如下

    return time.strftime('%Y_%m_%d %H:%M:%S',time.localtime())
    

    但是当解释器执行完这行代码, 它重新定义了 getXXXTime 这个变量 为 后面 sayLocal(getXXXTime) 调用的返回值。

    sayLocal(getXXXTime) 调用的返回值是什么呢?

    我们看到 sayLocal(getXXXTime) 函数的定义是这样的:

    1. def sayLocal(func):
    2. def wrapper():
    3. curTime = func()
    4. return f'当地时间: {curTime}'
    5. return wrapper

    所以, 它的参数 func 传入的是一个函数对象 ,就是原来的 getXXXTime 函数。

    然后这个函数 内部 又定义了一个函数 wrapper, 这个内部的函数 实现了在原来的getXXXTime 函数 调用的结果前面加上 当地时间: 这样的前缀。

    这个sayLocal 函数调用的返回值 就是 这个内部函数 wrapper。

    那么getXXXTime 经过装饰后,其实 已经变成了 内部函数 wrapper

    所以后面我执行调用 getXXXTime 函数,其实就是调用 wrapper。

    所以我们说 sayLocal 函数 就是一个装饰器,它装饰了参数函数的行为。

    而 第 14行代码,就是 把一个函数名 对应的行为改变为 装饰后的 行为。

    Python 中 对应第14行代码, 可以有更方便的写法,如下

    1. import time
    2. def sayLocal(func):
    3. def wrapper():
    4. curTime = func()
    5. return f'当地时间: {curTime}'
    6. return wrapper
    7. @sayLocal
    8. def getXXXTime():
    9. return time.strftime('%Y_%m_%d %H:%M:%S',time.localtime())
    10. print (getXXXTime())

     @sayLocal 开头后面接装饰器函数 的这种写法,是一种 语法糖 ,也就是便捷写法。

    当Python解释器执行 完 下面语句的时候,

    1. @sayLocal
    2. def getXXXTime():
    3. ....

    就等于执行了这样的一条语句

    getXXXTime = sayLocal(getXXXTime)
    

    这样写起来比较方便快捷。


    可能有的读者会问,干嘛要这么麻烦? 我们直接改这个函数的内容,不是更简单吗?像这样

    1. import time
    2. def getXXXTime():
    3. curTime = time.strftime('%Y_%m_%d %H:%M:%S',time.localtime())
    4. return f'当地时间: {curTime}'

    装饰器经常被用在库和框架中, 给别的开发者使用。

    这些库的开发者预料到 使用者 开发的函数可能需要 一些增强的功能。

    但是 这些库的开发者 没法去改使用者的代码, 就可以把这些增强的部分做在 装饰器函数中。

    这样使用者,只需要在他们的函数前面上@xxx 就使用了这些增强的功能了。

    被装饰的函数有参数

    如果要装饰函数有 未知个数的参数, 怎么办呢?

    像这样的2个函数,都要装饰

    1. import time
    2. def getXXXTimeFormat1(name):
    3. curTime = time.strftime('%Y-%m-%d %H:%M:%S',time.localtime())
    4. return f'{curTime} ,数据采集者:{name} '
    5. def getXXXTimeFormat2(name,place):
    6. curTime = time.strftime('%Y-%m-%d %H:%M:%S',time.localtime())
    7. return f'{curTime} ,数据采集者:{name} , 采集地:{place}'

    这时,可以使用函数的可变参数,像这样

    1. import time
    2. def sayLocal(func):
    3. def wrapper(*args,**kargs):
    4. curTime = func(*args,**kargs)
    5. return f'当地时间: {curTime}'
    6. return wrapper
    7. @sayLocal
    8. def getXXXTimeFormat1(name):
    9. curTime = time.strftime('%Y-%m-%d %H:%M:%S',time.localtime())
    10. return f'{curTime} ,数据采集者:{name} '
    11. @sayLocal
    12. def getXXXTimeFormat2(name,place):
    13. curTime = time.strftime('%Y-%m-%d %H:%M:%S',time.localtime())
    14. return f'{curTime} ,数据采集者:{name} , 采集地:{place}'
    15. print (getXXXTimeFormat1('张三'))
    16. print (getXXXTimeFormat2('张三',place='北京'))

    其中 *args 可以接受一切 不指定参数名 的传参方式 , 比如 '张三'

    **kargs 可以接受一切 指定参数名 的传参方式 , 比如 place='北京'

    装饰器函数自身有参数

    有时候,装饰器函数自身需要有参数。比如要web服务端开发,对执行某个API处理前,要检查系统是否激活了对应功能,那么我们的装饰器函数需要有参数。

    比如:

    1. # 需要激活自动化测试功能
    2. @activation_check('自动化测试')
    3. def autotest_scene1(request):
    4. ...
    5. # 需要激活性能测试功能
    6. @activation_check('性能测试')
    7. def loadtest_scene1(request):
    8. ...

    使用 有参数的装饰器 装饰一个函数,其本质等同于

    func_name = (decorator(params))(func_name)
    

    所以,我们应该这样定义

    1. from activations import activatedCapList
    2. def activation_check(capability):
    3. def wrapper1(func):
    4. def wrapper2(*args, **kwargs):
    5. # 如果该功能没有激活,直接返回
    6. if capability not in activatedCapList:
    7. return Response('功能未激活')
    8. # 已经激活,调用被装饰的函数进行处理
    9. return func(*args, **kwargs)
    10. return wrapper2
    11. return wrapper1
    12. @activation_check('自动化测试')
    13. def autotest_scene1(request):
    14. ...
    15. @activation_check('性能测试')
    16. def loadtest_scene1(request):
    17. ...

    autotest_scene1 函数被装饰后,就等于调用了 :

    autotest_scene1 = (activation_check(capability))(autotest_scene1)
    

    过程是这样的:

    activation_check(capability) 的返回值 就是 wrapper1 函数,

    wrapper1(autotest_scene1) 的返回值 就是 wrapper2 函数,

    所以被装饰后,实际上调用 autotest_scene1 就变成了调用 wrapper2,

    wrapper2是包含了两个外部对象 capability、 func 的闭包函数

  • 相关阅读:
    通用HttpClient封装
    python 爬虫的开发环境配置
    C++文件操作解析及使用(读、写文件 使用文件指针)
    Ubuntu Postgresql开机自启动服务
    如何给我们项目中的node_modules里面的包打补丁
    ActiveReportsJS 在 React 应用程序中添加和自定义查看器
    Linux内核分析(十八)--内存管理之虚拟地址与物理内存分配机制
    行列式的性质
    c#扩展包-Stateless
    使用amp训练出现Detected call of lr_scheduler.step() before optimizer.step().
  • 原文地址:https://blog.csdn.net/weixin_47649808/article/details/126325850