• (十)Python装饰器


    装饰器:本质就是函数,功能是为其他函数添加附加功能。

    两个原则:

    1.不修改被修饰函数的源代码

    2.不修改被修饰函数的调用方式

     一个栗子

    def test():
        res = 0
        for i in range(50):
            res += i
        print(res)
    test()
    结果:1225

    需求:要想计算上述函数的运行时间,既不修改被修饰函数的源代码,又不修改被修饰函数的调用方式,关键还要能添加进附加功能,如何实现?

    如下,初级版本一直接修改了被修饰函数的源代码

    import time
    def test():
        start_time = time.time()
        res = 0
        for i in range(50):
            time.sleep(0.1)
            res += i
        print(res)
        end_time = time.time()
        print('执行的时间 %s' %(end_time - start_time))
    test()
    结果:1225
    执行的时间 5.027297258377075

    这么做是不可行的,如果有一百个函数呢,需要去改一百个函数的源代码。

    Python装饰器的知识要点:

    装饰器=高阶函数+函数嵌套+闭包

    我们从这三个知识点,循序渐进,一点点演变,最后完成上述的需求

    先看使用高阶函数的版本二:

    import time                  //被修饰函数的源代码没有动
    def test():
        res = 0
        for i in range(50):
            res += i
        print(res)
    
    def print_time(func):
        start_time = time.time()
        func()
        time.sleep(1)
        end_time = time.time()
        print('执行的时间 %s' %(end_time - start_time))
    
    print_time(test)              //被修饰函数的调用方式被修改了
    结果:1225
    执行的时间 1.0009217262268066

    被修饰函数的源代码确实没有修改,但是被修饰函数的调用方式发生了变化,不合格。

    再看使用高阶函数的版本三:

    import time
    def test():
        res = 0
        for i in range(50):
            res += i
        print(res)
    
    def print_time(func):
        start_time = time.time()
        func()
        time.sleep(1)
        end_time = time.time()
        print('执行的时间 %s' %(end_time - start_time))
        return func            //这步是关键,把当参数穿进去的函数,再return回来,为了后面赋值给原本的函数名test,这样就可以不修改被修饰函数的调用方式
    
    test = print_time(test)    //赋值给原本的函数名test
    test()                     //被修饰函数的调用方式没有变化
    结果:1225
    执行的时间 1.0000982284545898
    1225

    看似符合要求了,但是我们看运行结果,test()被执行了两遍,还是不合格。

    所以光靠高阶函数,无法达到目标,现在来看函数嵌套和闭包

    函数嵌套是指在一个函数里定义另一个函数,而不是在一个函数里调用另一个函数

    闭包:如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure)。它只不过是个“内层”的函数,由一个名字(变量)来指
    代,而这个名字(变量)对于“外层”包含它的函数而言,是本地变量。

     

    结果为:from grandson nick

    这个例子就是函数嵌套+闭包

    执行grandson(),先从自己内部找name这个变量,没有的话从son()找,没有的话再从super()找,最后就用了super()传进来的参数,这其实就是函数作用域。

    也就是说,从最外层super()传一个参数,里面嵌套的函数都可以接受到这个参数

    高阶函数+函数嵌套+闭包的版本四:

    import time
    def test():           
        time.sleep(3)
        print('函数执行完毕')
    
    def timer(func):      
        def wrapper():
            start_time = time.time()
            func()        //闭包,取的是函数外部的参数,就是在运行test()
            end_time = time.time()
            print('运行时间%s' %(end_time - start_time))
        return wrapper
    test = timer(test)    //返回的是wrapper的地址
    
    test()                //执行的是wrapper()

    结果:函数执行完毕
    运行时间3.0003645420074463

    到此,没有改原函数的源代码,没有改变调用方式,并实现了附加功能。

    但是在调用函数之前还多了一步赋值:test = timer(test)

    所以这里加入Python的语法糖,在需要被修饰的函数前加上@timer装饰器

    最终版本为:

    import time
    def timer(func):
        def wrapper():
            start_time = time.time()
            func()
            end_time = time.time()
            print('运行时间%s' %(end_time - start_time))
        return wrapper
    @timer       //就相当于test=timer(test)
    def test():
        time.sleep(3)
        print('函数执行完毕')
    test()
    结果:函数执行完毕
    运行时间3.0003645420074463

     这里又出现一个问题,那就是如果被修饰函数有return返回值,执行test()将拿不到返回值,如下

    import time
    def timmer(func):
        def wrapper():
            start_time = time.time()
            func()
            end_time = time.time()
            print('函数执行的时间%s' %(end_time - start_time))
        return wrapper
    
    @timmer
    def test():
        time.sleep(2)
        print('test函数执行结束')
        return 123
    
    # test = timmer(test)
    res = test()
    print(res)
    结果:test函数执行结束
       函数执行的时间2.000626802444458
       None //拿不到返回值

    因为test()执行的时候,其实执行的是wrapper(),而wrapper()是没有返回值的

    所以再来一个改良版,加上返回值的装饰器:

    import time
    def timmer(func):
        def wrapper():
            start_time = time.time()
            res = func()                //把test()的返回值保存为res
            end_time = time.time()
            print('函数执行的时间%s' %(end_time - start_time))
            return res                  //执行wrapper()时返回test()的返回值
        return wrapper
    
    @timmer
    def test():
        time.sleep(2)
        print('test函数执行结束')
        return 123
    
    # test = timmer(test)
    aaa = test()
    print(aaa)
    结果:test函数执行结束
       函数执行的时间2.0007336139678955
       123

     上述被修饰函数test()是没有参数的,如果是带参数的怎么办?

    import time
    def timmer(func):
        def wrapper(name,age):         //wrapper()需要加上被修饰函数的参数,因为test('nick',25)实际上是在执行wrapper('nick',25),不加会报错
            start_time = time.time()
            res = func(name,age)       //这里才是实际执行test()函数,所以也要加上参数
            end_time = time.time()
            print('函数执行的时间%s' %(end_time - start_time))
            return res
        return wrapper
    
    @timmer  # test = timmer(test)
    def test(name,age):
        time.sleep(2)
        print('test函数执行结束'  ' 姓名%s' ' 年龄%s'%(name,age))
        return 123
    
    aaa = test('nick',25)
    print(aaa)
    结果:test函数执行结束 姓名nick 年龄25
       函数执行的时间2.000373125076294
       123

    又有一个新问题,被修饰函数的参数个数是不固定的,如果再来一个test2()有三个参数,就无法被timmer()装饰

    继续改良升级版,加上返回值+参数的装饰器

    import time
    def timmer(func):
        def wrapper(*args,**kwargs):       //wrapper()接受任意数量的参数
            start_time = time.time()
            res = func(*args,**kwargs)     //把wrapper()接受的参数原封不动传给test()
            end_time = time.time() 
            print('函数执行的时间%s' %(end_time - start_time))
            return res
        return wrapper
    
    @timmer  # test = timmer(test)
    def test(name,age):
        time.sleep(2)
        print('test函数执行结束'  ' 姓名%s' ' 年龄%s'%(name,age))
        return 123
    
    aaa = test('nick',25)
    print(aaa)
    结果:test函数执行结束 姓名nick 年龄25
       函数执行的时间2.0007336139678955
       123

     到此,不修改被修饰函数的源代码+不修改被修饰函数的调用方式+新增附加功能+加参数+有返回值的装饰器完成。

    最后来一个例子,模拟电商系统的用户认证与session会话保持的例子:

    #当前已注册的用户                                //一般是数据库,记录已存在的用户信息,这里用list代替
    user_list=[
        {'name':'nick','passwd':'123'},
        {'name':'xiaodong','passwd':'123'},
        {'name':'xiaohu','passwd':'123'},
        {'name':'xiaowang','passwd':'123'},
    ]
    #当前的登录状态                                   //这里就是记录session的登录状态
    nowlogin_dic = {'usrname':None,'login':False}
    
    def vali(login_type='jdbc'):         #为了传一个参数,判断哪一种认证方式,默认为jdbc数据库
        def validate(func):
            def wrapper(*args,**kwargs):
                if login_type =='jdbc':
                    if nowlogin_dic['usrname'] and nowlogin_dic['login']:
                        res = func(*args, **kwargs)
                        return res
                    username = input('请输入用户名:').strip()
                    passwd = input('请输入密码:').strip()
                    for user_dic in user_list:
                        if username == user_dic['name'] and passwd == user_dic['passwd']:
                            nowlogin_dic['usrname'] = username
                            nowlogin_dic['login'] = True
                            res = func(*args, **kwargs)
                            return res
                    else:
                        print('用户名密码错误')
                elif login_type == 'ldap':
                    print('ldap都通过认证')
                    res = func(*args, **kwargs)
                    return res
                else:
                    print('不通过!!!')
                    res = func(*args, **kwargs)
                    return res
            return wrapper
        return validate
    @vali()  #index = validate(index)
    def index():                #不传参数就默认认证方式为jdbc
        print('欢迎来到主页')
    @vali(login_type='ldap')    //这一步是关键,@vali(login_type='ldap')返回一个validate,所以就相当于@validate,即home = validate(home)  ----这么做就是在实际的装饰器validate外面套了一层,为了多一个参数来区别认证方式
    def home():
        print('欢迎回家')
    @vali(login_type='haha')
    def shopping_car():
        print('购物车里有 %s %s %s' % ('茶杯', '牛奶', '香蕉'))
    
    index()
    home()
    shopping_car()

    结果:

    请输入用户名:nick
    请输入密码:123
    欢迎来到主页
    ldap都通过认证
    欢迎回家
    不通过!!!
    购物车里有 茶杯 牛奶 香蕉

    
    
  • 相关阅读:
    批处理命令系列
    CMD批处理之PC查看连过的WIFI密码
    数据结构与算法之各种方法遍历二叉树
    二叉树同构判定算法
    卡拉兹(Callatz)猜想
    Java之字符串比较大小
    Java报错之StackOverflowError
    火绒勒索病毒诱捕技术浅析
    数据结构与算法之二叉链树
    数据结构与算法之广义表的基本运算
  • 原文地址:https://www.cnblogs.com/xulan0922/p/10119127.html
Copyright © 2020-2023  润新知