美文网首页Python
13. Python之装饰器

13. Python之装饰器

作者: 随便写写咯 | 来源:发表于2021-01-18 00:02 被阅读0次

    1 什么是装饰器

        器指的是工具,可以定义成函数
        装饰指的是为其他事物添加额外的东西点缀
        
    
        合到一起的解释:
            装饰器指的定义一个函数,该函数是用来为其他函数添加额外的功能
    

    2 为何要用装饰器

        开放封闭原则
            开放:指的是对拓展功能是开放的
            封闭:指的是对修改源代码是封闭的
            
        装饰器就是在不修改被装饰器对象源代码以及调用方式的前提下为被装饰对象添加新功能
    

    3 如何使用装饰器

    需求:在不修改index函数的源代码以及调用方式的前提下为其添加统计运行时间的功能
    
    def index(x,y):
        print('index %s %s' % (x, y)) # 统计index函数的运行时间, 就是统计其函数体代码的运行时间, 这里就是print运行的时间
    index(111,222)
    
    方案1: 此方案利用time模块, 计算代码运行时间, 虽然没有修改函数的调用方式, 但是修改了源代码
    
    import time
    
    def index(x,y):
        start = time.time()
        time.sleep(3)
        print('index %s %s' % (x, y))
        stop = time.time()
        print(stop - start)
    index(111,222)
    >>
    index 111 222
    3.0093071460723877
    
    方案2: 虽然没有修改源代码, 也没有修改调用方式, 但是重复调用统计时间功能时, 需要复制粘贴多次. 
    
    import time
    
    def index(x,y):
    
        time.sleep(3)
        print('index %s %s' % (x, y))
    
    
    start = time.time()
    index(111,222)
    stop = time.time()
    print(stop - start)
    >>
    index 111 222
    3.0013160705566406
    
    方案3: 当设计重复调用有, 可以把调用的代码写到函数, 但是, 此时虽然, 减少了代码冗余的问题, 也没有修改被装饰对象index的源代码, 可是调用方式变成了wrapper(), 原来的调用方式是index(). 除此之外, wrapper函数是写死的, index(111,222)需要修改
    
    import time
    
    def index(x,y):
    
        time.sleep(3)
        print('index %s %s' % (x, y))
    
    def wrapper():
        start = time.time()
        index(111,222)
        stop = time.time()
        print(stop - start)
    
    wrapper()
    >>
    index 111 222
    3.0009148120880127
    
    方案3优化1: 此时, 可以直接通过wrapper的实参, 给index的形参传值, 但是, 一旦index的形参发生变化, 那么wrapper的形参, 实参以及wrapper里的index()实参都要跟着改
    
    
    import time
    
    def index(x,y):
    
        time.sleep(3)
        print('index %s %s' % (x, y))
    
    def wrapper(a,b): # a= 111, b = 222
        start = time.time()
        index(a,b) # index(111,222)
        stop = time.time()
        print(stop - start)
    
    wrapper(111,222)
    >>
    index 111 222
    3.000406503677368
    
    方案3优化2: 利用*args 和 **kwargs实现传参, 此时, wrapper可以给index传任何参数, 只要符合index形参语法要求即可. 此时index写活了, 但是如果想装饰其他函数, 还要再写一遍wrapper, 又出现了代码冗余
    
    import time
    
    def index(x,y,z):
    
        time.sleep(3)
        print('index %s %s %s' % (x, y, z))
    
    def wrapper(*args,**kwargs):# args = (111,), kwargs = {'z':333,'y':222}
        start = time.time()
        index(*args,**kwargs) # *(111,), **{'z':333, 'y':222} >> 111, z=333, y=222
        stop = time.time()
        print(stop - start)
    
    wrapper(111,z=333,y=222)
    
    方案3优化3: 
    添加了可变长参数后, wrapper函数并没有写活, 其只能为index函数提供执行时间统计
    因此, 还要把wrapper函数写活, 让其在任意位置调用, 统计函数运行时间
    
    
    利用闭包, 通过outter装饰器给wrapper中的func传参, 实现把被装饰的函数写活
    让outter函数返回wrapper的内存地址, 再把outter(index)的返回值wrappe的内存地址赋值给index
    再让index()执行, 就相当于执行wrapper(). 
    这样对于调用者来说, 没有改变调用方式, 同时源代码也没有更改
    还实现乐同一个装饰器, 可以给不通过的函数使用
    
    import time
    
    
    def index(x,y,z):
        time.sleep(3)
        print('index %s %s %s'%(x,y,z))
    
    def outter(func):
        def wrapper(*args, **kwargs): 
    # 这里就是利用闭包, 为了把wrapper写活, 也就是在全局调用, 因此, 需要给wrapper包一个外层函数
    # 之后, wrapper中调用的真正的函数名func, 通过变量来定义, 由外层的函数给它传值
            start = time.time()
            func(*args,**kwargs)  
            # 这里必须用闭包给wrapper的func传参是因为, 如果用传统的形参实参去传参
            # 那么传的值会给到func()里的*args 和 **kwargs, 而不是给func变量名传值, 想给func传只能用闭包
            stop = time.time()
            print(stop-start)
        return wrapper # 让外层outter函数返回wrapper的内存地址
    index=outter(index) # 把需要计算的函数, index传给outter, 进而传给wrapper
    # 此时outter()中的index还是指向原来index函数的内存地址, 执行完
    # index=outter(index), index就变成了wrapper的内存地址了
    index(x=1,y=2,z=3) # 此时index已经是wrapper的内存地址, 就可以进一步执行wrapper函数
    ***********************************************************************************************************
    此时, 在方案3优化3的基础上, 可以用outter装饰任何函数
    
    import time
    
    
    def index(x,y,z): # 被装饰对象
        time.sleep(3)
        print('index %s %s %s'%(x,y,z))
    
    def home(name):
        time.sleep(3)
        print('welcome to {} home'.format(name))
    
    def outter(func):
        def wrapper(*args, **kwargs):
            start = time.time()
            func(*args,**kwargs)
            stop = time.time()
            print(stop-start)
        return wrapper
    
    index=outter(index)
    home=outter(home)
    # index和home指向相同的wrapper的内存地址, 相同的功能
    # 但是不是相同的wrapper, 因为参数不一样, 一个outter给的index, 一个outter给的home
    
    index(x=1,y=2,z=3)
    home('admin')
    >>
    index 1 2 3
    3.0018913745880127
    welcome to admin home
    3.0132527351379395
    
    
    方案3优化4: 在优化3的基础上, 被装饰对象的函数返回值是无法获取的, 以home()为例. 
    
    import time
    
    def home(name):
        time.sleep(3)
        print('welcome to {} home'.format(name))
        return 123 # home函数返回123
    
    def outter(func):
        def wrapper(*args, **kwargs):
            start = time.time()
            func(*args,**kwargs)
            stop = time.time()
            print(stop-start)
        return wrapper
    
    home=outter(home)  # 这里outter内的home还是原来home的内存地址, 但是执行后, 赋值给home, 这个home就是wrapper的内存地址了
    res=home('admin') # 因此, 这里执行home('admin')实际执行的是wrapper('admin'), 也就拿不到原本home的返回值
    print('home 返回值', res)
    
    *********************************************解决方案***************************************************
    wrapper中的func(*args,**kwargs)实际返回的就是被装饰函数, 就是home()的返回值, 那么可以把func(*args,**kwargs)赋值给一个变量, 让wrapper函数return这个变量, 这样执行res=home('admin'), 这个res就是wrapper的返回值, wrapper的返回值就是home()的返回值
    
    
    import time
    
    def home(name):
        time.sleep(3)
        print('welcome to {} home'.format(name))
        return 1234 # home函数返回1234
    
    def outter(func):
        def wrapper(*args, **kwargs):
            start = time.time()
            res=func(*args,**kwargs)
            stop = time.time()
            print(stop-start)
            return res
        return wrapper
    
    
    home=outter(home)
    res=home('admin')
    print('home 返回值', res)
    >>
    welcome to admin home
    3.0078957080841064
    home 返回值 1234
    *******************到此,wrapper函数借助装饰器, 就伪装的和原函数一模一样了*****************
    

    4 语法糖

    实现了装饰器后, 每次需要用装饰器装饰函数时, 都需要执行两行代码
    
    home=outter(home)
    home('admin')
    
    index=outter(index)
    index(11,22,33)
    
    实现语法糖
    
    
    import time
    
    def timer(func): # 给装饰器起个名字, 然后定义在被装饰的函数前面
        def wrapper(*args, **kwargs):
            start = time.time()
            res=func(*args,**kwargs)
            stop = time.time()
            print(stop-start)
            return res
        return wrapper
    
    @timer   # 在需要被装饰函数上面一行使用@timer, 就相当于执行了index=timer(index), 这时index变成了wrapper的内存地址, 之后下面在调用index(11,22,33), 实际就是运行wrapper(11,22,33)
    def index(x,y,z):
    
        time.sleep(3)
        print('index %s %s %s' % (x, y, z))
    
    @timer
    def home(name):
        time.sleep(3)
        print('welcome to {} home'.format(name))
        return 1234
    
    
    
    home('admin')
    index(11,22,33)
    
    >>
    welcome to admin home
    3.000441312789917
    index 11 22 33
    3.0028538703918457
    *********如果不需要装饰器内的功能, 而是使用函数的原始功能, 只需要把@timer注释掉即可*****
    

    5 叠加多个装饰器,加载顺序与运行顺序

    def deco1(func1):
        def wrapper1(*args, **kwargs):
            print('正在运行====>deco1.wrapper1')
            res1 = func1(*args, **kwargs) # func1拿到的是wrapper2的内存地址
            return res1
        return wrapper1
    
    def deco2(func2):
        def wrapper2(*args, **kwargs):
            print('正在运行====>deco2.wrapper12')
            res2 = func2(*args, **kwargs)  # func2拿到的是wrapper3的内存地址
            return res2
        return wrapper2
    
    def deco3(x,y):
        def outter3(func3):
            def wrapper3(*args, **kwargs):
                print('正在运行====>deco3.outter3.wrapper3')
                res3 = func3(*args, **kwargs)  # func3 = 被装饰对象index的内存地址, 也就是说func3拿到的才是被装饰对象index的函数体代码的内存地址,print('from index %s %s'%(x,y))
                return res3
            return wrapper3
        return outter3
    
    @deco1
    @deco2
    @deco3(1,2)
    def index(x,y):
        print('from index %s %s'%(x,y))
    
    index(1,2)
    
    加载顺序: 自下而上
    
    1. 运行@deco3(1,2) >>>> outter3的内存地址 结果就是@outter3
    2. 运行@outter3 >>>> index = outter(index) >>>> 结果 index = wrapper3的内存地址
    3. 运行deco2, deco2会把下方的函数(index)内存地址作为参数传给func2, 但是经过了deco3(1,2)的转换, 此时index的内存地址已经指向了wrapper3, 而不是原来的index了 >>>> index = deco2(wrapper3的内存地址) >>>> index = wrapper2的内存地址
    4. 运行deco1, deco1(此时index指向了wrapper2的内存地址), 将wrapper2的内存地址给了func1, index = deco1(wrapper2的内存地址), >>>> index = wrapper1的内存地址
    
    print(index)
    <function deco1.<locals>.wrapper1 at 0x00000204041B6940>
    
    执行顺序: 自上而下
    
    1. 运行index(1,2)会先调wrapper1, wrapper1中运行了func1, 而func1拿到的是wrapper2的内存地址, 因此运行func1(*args, **kwargs), 实际运行的是wrapper2
    2. 运行wrapper2, wrapper2中运行了func2, 而func2又指向了wrapper3的内存地址, 因此运行了func2就是运行wrapper3
    3. 运行wrapper3, wrapper3中运行了func3, 而func3拿到的是index的内存地址, 因为, 运行func3就会运行index内的函数体代码,print('fro index....')
    4. 等到index内的代码运行完, index先结束, 然后wrapper3结束, 然后wrapper2结束, 然后wrapper1结束
    

    6 总结无参装饰器模板

    def 装饰器名称(被装饰的函数名): # 定义装饰器
        def wrapper(*args,**kwargs): # wrapper伪装成被装饰对象
            # 1、调用原函数
            # 2、为其增加新功能
            res=被装饰函数名(*args,**kwargs) # 被装饰对象
            return res # 让wrapper返回被装饰对象的函数执行返回值
        return wrapper # 让装饰圈outter返回wrapper的内存地址
    
    利用以上的模板, 只需要在wrapper函数代码体中, 添加新的功能, 就可以给被装饰的func(*args,**kwargs)添加新的功能
    
    案例1: 编写统计函数运行时间的装饰器
    import time
    
    def timer(func):
        def wrapper(*args,**kwargs):
            # 1、调用原函数
            # 2、为其增加新功能
            start = time.time()
            res=func(*args,**kwargs)
            stop = time.time()
            print(stop-start)
            return res
        return wrapper
    
    @timer
    def index(x,y,z):
        time.sleep(2)
        print('index', x,y,z)
    
    index(11,22,33)  # 只要装饰器写好, 调用index就是和没有装饰器时一样
    
    案例2: 编写认证功能装饰器
    
    import time
    
    username = 'admin'
    password = 'admin'
    
    def timer(func):
        def wrapper(*args,**kwargs):
            # 1、调用原函数
            # 2、为其增加新功能
            start = time.time()
            res=func(*args,**kwargs)
            stop = time.time()
            print(stop-start)
            return res
        return wrapper
    
    def auth(func):
        def wrapper(*args,**kwargs):
            _username = input('请输入账号: ').strip()
            _password = input('请输入密码: ').strip()
            if _username == username and _password == password:
                res = func(*args,**kwargs)
                return res
            else:
                print('wrong info')
        return wrapper
    
    
    @auth
    def index(x,y,z):
        time.sleep(2)
        print('index', x,y,z)
    
    index(11,22,33)
    

    语法糖补充:

    #1 print('hello')是没有返回值的, print本身就是个函数, 如果没有定义返回值那么返回None
    var = print('hello')
    print(var)
    
    @print('hello')
    def home(a, b, c):
        pass
    >>
    TypeError: 'NoneType' object is not callable
    #2 执行上面代码会报错是因为@print('hello')会优先执行print('hello'), 然后得到其返回值None
       @None 就相当于 home = None(home), 而None是不可调用的函数, 因为会报错
    
    总结: print()是没有返回值的, 在@中, 利用的是返回值而不是结果, 此外@名字 就相当于 home = 名字(home) 去执行
    

    无参装饰器补充:

    如何将wrapper伪装的和原函数一样
    将原函数名指向的内存地址偷梁换柱成wrapper函数, 所以应该将wrapper做的跟原函数一样才行
    
    from functools import wraps
    
    def outter(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            """这个是主页功能"""
            res = func(*args, **kwargs) # res=index(1,2)
            return res
    
        # 手动将原函数的属性赋值给wrapper函数
        # 1、函数wrapper.__name__ = 原函数.__name__
        # 2、函数wrapper.__doc__ = 原函数.__doc__
        # wrapper.__name__ = func.__name__
        # wrapper.__doc__ = func.__doc__
    
        return wrapper
    
    @outter # index=outter(index)
    def index(x,y):
        """这个是主页功能"""
        print(x,y)
    
    
    print(index.__name__)
    print(index.__doc__) #help(index)
    
    index(1,2) # wrapper(1,2)
    

    7 有参装饰器

    被装饰对象的参数, 由wrapper传
    wrapper内的被装饰对象的内存地址, 由outter给传
    wrapper的形参无法修改, 因为wrapper的参数最终要传给被装饰对象
    outter的形参也无法修改, 因为outter的形参只能有一个, 是用来接受被装饰对象的内存地址的
    outter内如果还需要被传参, 就需要再包一层函数, 如果不再包一层函数, 就没法用语法糖码因为语法糖规定, outter只能接一个形参, 就是被装饰对象的内存地址
    
    index的参数什么样, wrapper的参数就该什么样, wrapper(*args,**kwargs)
    index的返回值什么样, wrapper的返回值就该什么样, res = func(*args,**kwargs); return res
    index的属性什么样, wrapper的返回值就该什么样, from functools import wraps; @wraps(func)
    
    from functools import wraps
    
    def auth(auth_type):
        def outter(func): # outter作为语法糖使用, 只能接受一个形参, 然后传给wrapper内的func变量, 所以要在其外层再包一个函数, 通过外层函数给outter内的函数传参,有了第三层函数, 就没有传参的限制了, 可以给outter和wrapper内传任意参数
            @wraps(func)  
            def wrapper(*args, **kwargs):
    
                _username = input('Username: ').strip()
                _password = input('Password: ').strip()
                if auth_type == 'File':
                    if _username == 'admin' and _password == 'admin':
                        print('Authorised from File')
                        res = func(*args, **kwargs)
                        return res
                    else:
                        print('Wrong Credential!')
                elif auth_type == 'DB':
                    if _username == 'admin' and _password == 'admin':
                        print('Authorised from DB')
                        res = func(*args, **kwargs)
                        return res
                    else:
                        print('Wrong Credential!')
                elif auth_type == 'LDAP':
                    if _username == 'admin' and _password == 'admin':
                        print('Authorised from LADP')
                        res = func(*args, **kwargs)
                        return res
                    else:
                        print('Wrong Credential!')
                else:
                    print('Non-Support Auth Type!')
    
            return wrapper
        return outter
    
    
    @auth('File')  # auth('File') 其实并不是真的语法糖, 只是用来给outter传参, auth('FIle')就是outter, 实际的语法糖还是outter, auth最外层的作用就是给内层传参, 有了auth, 内层的任意参数都可以通过auth来传.
    def index(x,y): # index=outter(index), 所以, outter是只能接收一个参数的也就是真正调用的那个函数名
        print('Result from index')
    
    @auth('DB')
    def home(x,y):
        print('Result from home')
    
    @auth('LDAP')
    def transfer(x,y):
        print('Result from transfer')
    
    
    
    index(1,2)
    home(3,4)
    transfer(5,6)
    
    有参装饰器模板
    
    def 有参装饰器(x,y,z):
        def outter(func):
            def wrapper(*args, **kwargs):
                res = func(*args, **kwargs)
                return res
            return wrapper
        return outter
    
    @有参装饰器(1,y=2,z=3)
    def 被装饰对象():
        pass
    

    相关文章

      网友评论

        本文标题:13. Python之装饰器

        本文链接:https://www.haomeiwen.com/subject/vkcwaktx.html