• 🍖装饰器


    一.什么是装饰器

    • 器 : 就是工具

    • 装饰 : 就是添加新功能

    '总结一句话来说' : 就是定义一个函数, 用该函数去为其他函数添加新功能
    

    二.为何要使用装饰器

    1.开放封闭原则

    • 针对上线的功能对拓展是开放的
    • 但是对修改源代码以及调用方式是封闭的
    '总结来说' : 装饰器就是在遵循开放封闭原则的前提下为被装饰对象添加新功能
    不修改被装饰对象的源代码
    不修改被装饰对象的调用方式
    

    2.在实际应用场景原因

    • 对上线软件的拓展功能, 我们必须提供扩展的可能性
    • 软件的源代码以及调用方式都应该避免被修改, 否则上线出现BUG之后不容易回滚

    3.装饰器与被装饰的对象均可以是任意可调用的对象

    • 装饰器----->函数
    • 被装饰的对象----->函数

    三.无参装饰器的优化历程 (助于理解)

    1.无参装饰器

    需求 : 为 index 函数加上计时功能

    import time  # time模块
    #time.time() 返回的是时间戳,从1970年1月1日凌晨到现在的秒数
    def index():
        time.sleep(1)
        print('from index')
    
    index()
    
    • 版本一 : 直接在函数体内部设置计时
    import time
    
    def index():
        start=time.time()     #开始时间
        time.sleep(1)         #睡"1"秒
        print('from index')
        stop=time.time()      #结束时间
        print('run time is %s' %(stop - start))  #运行时间
    
    index()
    结果 : 没有修改调用方式,但修改了源代码
    
    • 版本二 : 在调用函数的阶段进行计算时间
    import time
    
    def index():
        time.sleep(1)
        print('from index')
    
    start=time.time()
    index()
    stop=time.time()
    print('run time is %s' %(stop - start))
    结果 : 没有修改调用方式,没有修改源代码,但代码冗余
    
    • 版本三 : 使用闭函数的方式在"wrapper"函数里面调用"index"
    import time
    
    def index():
        time.sleep(1)
        print('from index')
    
    def wrapper():
        start=time.time()
        index()
        stop=time.time()
        print('run time is %s' %(stop - start))
    
    wrapper()
    结果 : 解决了代码冗余的问题,但把"index"写死了,只能计算"index"的运行时间
    
    • 版本四 : 将"index"当做参数传入
    import time
    
    def index():
        time.sleep(1)
        print('from index')
        
    def index2():
        time.sleep(1)
        print('from index2')
    
    def wrapper(f):
        start=time.time()
        f()  # 函数index的内存地址()
        stop=time.time()
        print('run time is %s' %(stop - start))
    
    wrapper(index)   # wrapper(函数index的内存地址)
    wrapper(index2)  # wrapper(函数index2的内存地址)
    结果 : 在上一个版本的基础上把被装饰的对象写活了,可以装饰任何无参函数,但还是改变了调用方式
    
    • 版本五 : 优化版本
    import time
    
    def index():
        time.sleep(1)
        print('from index')
    
    def outter(f):      # f=函数index的内存地址
        def wrapper():
            start=time.time()
            f()         # 函数index的内存地址()
            stop=time.time()
            print('run time is %s' %(stop - start))
        return wrapper
    
    index=outter(index)  # outter(函数index的内存地址)
    index()              #偷梁换柱法,让使用者感觉不到变化,之前怎么使用"index",现在还是怎么使用
    结果 : 没有改变调用方式,没有改变源代码,还实现了功能
    

    2.无参装饰器的雏形

    以上版本对于不需要参数传入的函数使用没有问题, 但对有参数传入函数进行装饰就会报错

    • 基于上面的版本进行进一步的优化,
    def home(name):
        time.sleep(2)
        print('home page,welecome %s' %name)
    
    def outter(f):          # f=函数home的内存地址
        def wrapper(name):  # 设置形参可进行传值
            start=time.time()
            f(name)         # 函数home的内存地址(name)
            stop=time.time()
            print('run time is %s' %(stop - start))
        return wrapper
    
    home=outter(home)       # outter(函数home的内存地址)
    home("egon")            # 偷梁换柱
    结果 : 设置形参后可以进行传值,但也就只能对"home"这个函数进行装饰了,写死了!
    
    • 优化版本 : 可以传入任意的参数, 也可以不传参数, 利用可变长参数(*args)与(**kwargs)来实现
    • 前面函数参数已经对可变长参数(*args)与(**kwargs)进行了详细介绍,可以参考
    import time
    
    def index():
        time.sleep(1)
        print('from index')
    
    def home(name):
        time.sleep(2)
        print('home page,welecome %s' %name)
        
    def outter(f):                # f=函数home的内存地址
        def wrapper(*args,**kwargs):
            start=time.time()
            f(*args,**kwargs)     # 函数home的内存地址(name)
            stop=time.time()
            print('run time is %s' %(stop - start))
        return wrapper
    
    index=outter(index)            # outter(函数index的内存地址)
    index()                        # 偷梁换柱
    
    home=outter(home)              # outter(函数home的内存地址)
    home("egon")                   # 偷梁换柱
    结果 : 实现了可以装饰无参和有参,并且遵循了开放封闭原则,但是还有一个问题:就是没有返回值!
    
    • 最终优化版本 : 设置返回值
    import time
    
    def index():
        time.sleep(1)
        print('from index')
    
    def home(name):
        time.sleep(2)
        print('home page,welecome %s' %name)
        return 123                #有返回值的
    
    def outter(f):                # f=函数home的内存地址
        def wrapper(*args,**kwargs):
            start=time.time()
            res=f(*args,**kwargs)  # 函数home的内存地址(name),并接收返回值
            stop=time.time()
            print('run time is %s' %(stop - start))
            return res             # 设置返回值
        return wrapper
    
    index=outter(index)
    home=outter(home)
    
    res=index()
    print(res)  # None
    
    res=home("egon")
    print(res)  # 123
    结果 : 完美!
    

    四.无参装饰器的最终结果及应用

    1.无参装饰器语法糖

    最精简的装饰器, 将需要的功能往里面添加就行
    def outter(func):
        def wrapper(*args,**kwargs):
            res=func(*args,**kwargs)
            return res
        return wrapper
    
    @outter  #被装饰函数的正上方单独一行
    def test():
        pass
    

    2.计算时间示例

    "@"的作用就是将"index"的内存地址传递到"timmer"中"func=index"
    import time
    def timmer(func):
        def wrapper(*args,**kwargs):
            start_time=time.time()        #开始时间
            res=func(*args,**kwargs)
            stop_time=time.time()         #结束时间
            print(stop_time-start_time)   #使用时间
            return res
        return wrapper
    
    @timmer # index=timmer(index)
    def index():
        time.sleep(1)
        print('welcome to index page')
        return 122
    
    @timmer # home=timmer(home)
    def home(name):
        time.sleep(2)
        print('welcome %s to home page' %name)
    
    index()       # 调用该怎么调还是怎么调
    home('egon')  # 调用该怎么调还是怎么调
    

    3.认证用户示例

    实现的功能就是用户登入了才能使用"index"或者"home"
    import time
    current_user={
       'username':None, #判断用户是否已经登入,有值就是已登入,"None"就是未登入
        }
    
    def auth(func):
        def wrapper(*args,**kwargs):
            if current_user['username']:        # 这里开始判断了
                print('已经登陆过了')
                res=func(*args,**kwargs)        #如果已经登入就直接运行被装饰的函数
                return res
    
            uname=input('用户名>>: ').strip()    # 如果未登入则输密码
            pwd=input('密码>>: ').strip()
            if uname == 'egon' and pwd == '123':
                print('登陆成功')
                current_user['username']=uname
                res=func(*args,**kwargs)         
                return res
            else:
                print('用户名或密码错误')
        return wrapper
    
    @auth  #index=auth(index)
    def index():
        time.sleep(1)
        print('welcome to index page')
        return 122
    
    @auth  #index=auth(index)
    def home(name):
        time.sleep(2)
        print('welcome %s to home page' %name)
    
    index()
    home('egon')
    

    4.多个装饰器组合使用示例

    • 多个装饰器组合使用时, 加载顺序是自下而上, 而执行顺序是自上而下的(下面第七大段会详细解析)
    实现用户认证,还可以计算程序运行的时间
    ##############1.用户认证装饰器##################
    import time
    dic = {
        'username': None,
    }
    
    def login(func):
        def wrapper(*args, **kwargs):
            if dic['username']:
                print('已登入')
                res = func(*args, **kwargs)
                return res
    
            name = input('姓名>>:').strip()
            pwd = input('密码>>:').strip()
            if name == 'song' and pwd == '123':
                print('登入成功')
                dic['username'] = name
                res = func(*args, **kwargs)
                return res
            else:
                print('用户名或密码错误')
        return wrapper
    
    ###############2.计算时间装饰器###################
    def timer(func):
        def wrapper(*args,**kwargs):
            start_time=time.time()
            res=func(*args,**kwargs)
            stop_time=time.time()
            print('时间%s'%(stop_time-start_time))
            return res
        return wrapper
    
    ################3.开始装饰###################
    
    有前后顺序,这里计算 login()+index() 的时间(也就是加上了认证的时间)
    @timer    # timer在前
    @login
    def index():
        time.sleep(1)
        print('欢迎宋海星')
        return 125
    
    只计算程序的运行时间(正确顺序)
    @login
    @timer    #这里只计算 home() 的时间
    def home(name):
        time.sleep(2)
        print('欢迎%s' %name)
    
    index()
    home('hai')
    

    五.有参装饰器的实现

    1.有参装饰器语法糖

    def outter2(x):
        def outter(func):
            def wrapper(*args,**kwargs):
                res=func(*args,**kwargs)
                return res
            return wrapper
        return outter
    
    
    @outter2(11)  # 被装饰函数的正上方单独一行
    def hhh():
        pass
    

    2.有参装饰器示例

    • 需求 : 如果我们的装饰器也需要参数传入呢? 这该如何解决
    观察下面,装饰器函数内部需要传入一个值"xxx",从哪里来?
    def outter(func):
        def wrapper(*args,**kwargs):
        	if xxx == "login":
                res=func(*args,**kwargs)
                return res
            elif xxx == "login2":
                print("22222")
            elif xxx == "login3":
                print("33333")
        return wrapper
    
    • 很简单! 只需要在外面再包一层就行了
    简单语法
    def auth(xxx):  #再包一层
        def outter(func):
            def wrapper(*args,**kwargs):
                if xxx == "login":
                    res=func(*args,**kwargs)
                    return res
                elif xxx == "login2":
                    print("22222")
                elif xxx == "login3":
                    print("33333")
            return wrapper
        return outter  #同时也要有返回值
    
    • 示例实现
    通过不同的选择进行不同的身份认证(比如商家认证,买家认证等等)
    import time
    dic = {
        'username': None,
    }
    
    def auth(engine):
        def auth2(func):
            def wrapper(*args, **kwargs):
                if engine == 'login1':           #如果传入的是"login1",就进入这种身份的认证
                    if dic['username']:
                        print('已经登陆过了')
                        res = func(*args, **kwargs)
                        return res
    
                    name = input('用户名>>: ').strip()
                    pwd = input('密码>>: ').strip()
                    if name == 'egon' and pwd == '123':
                        print('登陆成功')
                        dic['username'] = name
                        res = func(*args, **kwargs)
                        return res
                    else:
                        print('用户名或密码错误')
    
    
                elif engine == 'login2':       #"login2"这种模式的身份认证
                    print('xx认证')
                elif engine == 'login3':       #"login3"
                    print('xxxxxx认证')
            return wrapper
        return auth2
    
    @auth('login3')         #以"login3"模式的身份认证来使用"index"
    def index():
        time.sleep(1)
        print('欢迎登入')
        return 123
        
    @auth('login1')         #以"login1"模式的身份认证来使用"home"
    def home(song):
        time.sleep(2)
        print('啧啧啧%s' % song)
    
    index()
    home('哈哈')
    

    六.wraps 装饰器

    1.wraps 有什么作用

    • 被装饰的函数 的一些属性值赋值给 装饰器函数(wrapper) ,最终让属性的显示更符合我们想看到的 (装的更像)

    2.先看未添加 wraps 装饰器时的属性

    • help() : 既查看函数注释文档有查看函数名
    • .__name__ : 查看函数名
    • .__doc__ : 查看函数的注释文档
    猜猜打印的是什么情况?
    import time
    def timmer(func):
        def wrapper(*args,**kwargs):
            "我是wrapper函数注释信息"  # 这里添加了wrapper的注释信息
            start = time.time()
            res = func(*args,**kwargs)
            stop = time.time()
            print(f"运行时间{stop-start}秒")
            return res
        return wrapper
    
    @timmer
    def hhh():
        "hhh函数注释信息"     ## 这里添加了hhh的注释信息
        print("欢迎欢迎")
    
    print(hhh.__name__)
    # 返回的是 wrapper 的函数名 "wrapper"
    print(hhh.__doc__)
    # 返回额是 wrapper 的注释信息 "我是wrapper函数注释信息"
    print(help(hhh))
    '''依然是 wrapper 的注释和名字
    Help on function wrapper in module __main__:
    
    wrapper(*args, **kwargs)
        我是wrapper函数注释信息
    
    None
    '''
    # 这不值得奇怪,因为装饰器的底层原理就是返回了 wrapper 函数,只不过是将其改了下函数名
    

    3.添加 wraps 装饰器

    • 通过添加 wraps 装饰器将原函数 hhh 的属性传给 wrapper 函数
    注意与上面示例做对比
    import time
    from functools import wraps  #导入模块
    def timmer(func):
        @wraps(func)  #在 wrapper 的正上方添加wraps装饰器,把原有函数的属性传给wrapper
        def wrapper(*args,**kwargs):
            "我是wrapper函数注释信息"
            start = time.time()
            res = func(*args,**kwargs)
            stop = time.time()
            print(f"运行时间{stop-start}秒")
            return res
        return wrapper
    
    @timmer
    def hhh():
        "hhh函数注释信息"
        print("欢迎欢迎")
    
    print(hhh.__name__)
    # hhh
    print(hhh.__doc__)
    # hhh函数注释信息
    print(help(hhh))
    '''
    Help on function hhh in module __main__:
    
    hhh()
        hhh函数注释信息
    
    None
    '''
    # 可以发现属性已经发生了传递,装的更像了
    

    七.叠加多个装饰器的底层原理分析

    • 叠加多个装饰器的加载顺序是自下而上的(了解)
    • 叠加多个装饰器的执行顺序是自上而下
    def outter1(func1):  # func1 = wrapper2的内存地址
        print('============>outter1')
        def wrapper1(*args,**kwargs):
            print('============>wrapper1')
            res1=func1(*args,**kwargs)
            return res1
        return wrapper1
    
    def outter2(func2):  # func2 = wrapper3的内存地址
        print('============>outter2')
        def wrapper2(*args,**kwargs):
            print('============>wrapper2')
            res2=func2(*args,**kwargs)
            return res2
        return wrapper2
    
    def outter3(func3):  # func3 = 被装饰函数也就是index的内存地址
        print('============>outter3')
        def wrapper3(*args,**kwargs):
            print('============>wrapper3')
            res3=func3(*args,**kwargs)
            return res3
        return wrapper3
    
    加载顺序自下而上,先是"outter3"开始将"index"传入自己内部,然后"outter2"把"outter3"的返回值传入自己...
    @outter1  # outter1(wrapper2)---->wrapper1的内存地址 (index=wrapper1)
    @outter2  # outter2(wrapper3)---->wrapper2的内存地址 (index=wrapper2)
    @outter3  # outter3(index)------->wrapper3的内存地址 (index=wrapper3)
    def index():
        print('from index')
    #只加载不执行输出结果
    '''
    ============>outter3
    ============>outter2
    ============>outter1
    '''
    #加载执行
    index()
    '''
    ============>outter3
    ============>outter2
    ============>outter1
    ============>wrapper1
    ============>wrapper2
    ============>wrapper3
    from index
    '''
    

    叠加多个装饰器示例在本文第四段第四小节

  • 相关阅读:
    04面向对象编程-01-创建对象 和 原型理解(prototype、__proto__)
    03标准对象-02-RegExp 正则表达式
    03标准对象-01-Date和JSON
    广度优先搜索
    Java虚拟机(三) —— 类加载
    业务开发(一)—— MySQL
    Java并发编程(一) —— Java内存模型JMM
    深度优先搜索
    Spark学习(一)
    清泉白石
  • 原文地址:https://www.cnblogs.com/songhaixing/p/14051171.html
Copyright © 2020-2023  润新知