• 第十七篇 Python函数之闭包与装饰器


    一. 装饰器

    装饰器:可以拆解来看,器本质就是函数,装饰就是修饰的意思,所以装饰器的功能就是为其他函数添加附加功能。

    装饰器的两个原则:

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

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

    实现装饰器的知识储备: 装饰器 = 高阶函数 + 函数嵌套 + 闭包

    高阶函数

    高阶函数的定义:

    1. 函数接收的参数是一个函数名

    2. 函数的返回值是一个函数名

    3.满足上述条件任意一个,都可以称之为高阶函数

    import  time
    # 示例1: 函数接收的参数是一个函数名
    # def foo():
    #     print("Hello")
    #
    # def test(func):
    #     print(func)
    #     func()
    #
    # test(foo)
    
    # 示例2:函数test接收的参数是一个函数名foo,可以直接为foo函数增加一个功能
    # 需求:想统计foo运行的时间,结果这个高阶函数修改了函数的源代码
    
    # def foo():
    #     time.sleep(2)
    #     print("Hello")
    #
    # def test(func):
    #     print(func)
    #     start_time = time.time()
    #     func()
    #     stop_time = time.time()
    #     print('函数的运行时间是:%s '%(stop_time-start_time))  # 统计的是func()的运行时间,即foo的运行时间
    #
    # test(foo)
    
    # 示例3:函数的返回值是一个函数名
    
    def foo():
        print("from the foo")
    def test(func):
        return func
    
    # 实现了装饰器的不改变函数的调用方式这个功能
    foo = test(foo)
    foo()
    
    
    # 示例4: 示例2+示例3就能实现一个半完整的装饰器
    def foo():
        time.sleep(3)
        print('来自foo')
    
    
    # 不修改foo源代码
    # 不修改foo调用方式
    # 没有修改被修饰函数的源代码,也没有修改被修饰函数的调用方式,但是也没有为被修饰函数添加新功能
    def timer(func):
        start_time = time.time()
        func()
        stop_time = time.time()
        print('函数的运行时间是:%s '%(stop_time-start_time))  # 统计的是func()的运行时间,即foo的运行时间
        return func
    foo = timer(foo)  # timer函数的返回值是foo,在把返回值又赋值给了foo,得到一个内存地址
    foo()   #  有了内存地址,直接加个()就可以运行。
    # 结果
    # 来自foo
    # 函数的运行时间是:3.001082420349121
    # 来自foo      # 上面说的是半完整的装饰器,是因为这里多运行了一次foo,肯定不行那,这就导致这个装饰器就不合格了

     函数嵌套

    # 函数嵌套及作用域
    # father()函数里嵌套了一层son()函数,son()函数里又嵌套了一层grandson()函数
    def father(name): print('from father %s' %name) # name变量的作用域,是传进来的DDD def son(): # 函数也是变量 name='abc' print('我的爸爸是%s' %name) # 作用域是son函数自己定义的name变量 def grandson(): print('我的爷爷是%s' % name) # grandson函数没有自己的name变量,需要往上一级一级找,找到了son函数,就是abc # 同调用son的道理一样,需要通过father调用son,通过son函数再调用grandson函数 grandson() # 如果要调用son函数,不能跑到father的外面去调用,会报错;只能通过调用father来调用son函数 # 此处是调用son函数 son() father('DDD')
    son('abcx') # 报错 # son函数定义在father里,所以只能通过father调用son函数

    闭包

    上面理解了函数的层级嵌套,就很好理解闭包的概念。嵌套的函数换个名称叫闭包

    包:就像一个盒子套着一个盒子,一层层往外套,每一次都是一个包

    闭:就是封装,封装变量。每个包都有被封装的变量。

    下面画图解释

    闭包如何找变量?

    还是先从自己的闭包里开始找变量,自己没有在往上一层包里找变量,如果还没有,可以一直往外找,可以一直找到最外层

     装饰器基本实现

    首先,
    # 搭个装饰器的架子,满足高阶函数,满足函数嵌套
    import time
    def timer(func):
        def wrapper():
            # print(func)   # 嵌套函数也可以获取传入的参数,传入的参数也是个函数,作用域在起作用  这是个内存地址
            func()        # 有了内存地址,加()就可以执行func函数
        return wrapper
    
    def test():
        time.sleep(2)
        print("test函数运行完毕")
    
    其次,
    有个需求:不修改test函数的原代码,也不修改test函数的调用方式,而且为test函数增加一个统计运行时间的功能
    
    实现该需求的流程分析及代码运行过程:
    
    # 流程分析:
    # 1. 把test函数传给timer,直接返回的是wrapper的地址,而没有运行wrapper函数
    res = timer(test)
    # 2. 上面返回的是wrapper函数的内存地址,所以就可以直接加()执行wrapper函数
    res()   # 执行的是wrapper函数
    # 3. 再看wrapper函数做了什么?
    # wrapper 函数运行的是func函数,而func函数就是传入的test()函数,所以执行wrapper就可以执行test()函数
    # 所以,这个时候就可以添加代码来统计test函数的运行时间了。
    
    至此,
    # 上面的代码就演化成为:
    def timer(func):
        def wrapper():
            start_time = time.time()
            func()
            stop_time = time.time()
            print("test函数的运行时长是: %s" % (stop_time - start_time))
        return wrapper
    
    def test():
        time.sleep(2)
        print("test函数运行完毕")
    
    
    res = timer(test)
    res()
    # 运行结果
    test函数运行完毕
    test函数的运行时长是: 2.000473976135254
    
    但是,这里有 个问题,需求是不修改test函数的调研方式,test函数的调用方式应该是test(),而这里的调用方式是res()
    
    所以,解决修改了test函数调用方式这个问题的方法:
    # 将timer(test)的值赋给变量test
    test = timer(test)
    # 然后再test()调用,这样就达到了没有修改test函数调用方式的目的
    test()
    
    #到这里,一个装饰器的架子和调用模型就初步成型了,但是还需要赋值给test变量这一步操作
    
    # 所以,最后,
    python为了实现这个不修改test函数调用方式,也省略赋值这行代码,而产生了一个装饰器的概念
    
    # 上面的代码就进化为:
    # 装饰器
    def timer(func):
        def wrapper():
            start_time = time.time()
            func()
            stop_time = time.time()
            print("test函数的运行时长是: %s" % (stop_time - start_time))
        return wrapper
    
    @timer   #在test函数的顶头加上 @timmer来修饰test函数,  就相当于 test=timmer(test) 这个赋值动作
    def test():
        time.sleep(2)
        print("test函数运行完毕")
    
    # 有了装饰器,就可以直接调用test函数
    test()
    
    # 终于啊,老天爷啊,历经千辛万苦,终于完美的满足了最开始的需求

    为闭包加上返回值

    还是接上面的基本装饰器来说

    def timer(func):
        def wrapper():
            start_time = time.time()
            func()
            stop_time = time.time()
            print("test函数的运行时长是: %s" % (stop_time - start_time))
        return wrapper
    
    # 需求:给test函数加一个返回值: “test函数返回好消息了”
    @timer  
    def test():
        time.sleep(2)
        print("test函数运行完毕")
        return "test函数返回好消息了"
        
    res=test()
    print(res)
    #结果
    test函数运行完毕
    test函数的运行时长是: 2.000929117202759
    None      返回None了
    
    # 为什么会这样呢?分析如下:
    # 1. 运行test函数,因为有装饰器,实际上运行的是wrapper这个闭包,而wrapper函数却没有返回值,所以返回的是None,那么给wrapper函数加个返回值呢?
    代码如下:
    def timer(func):
        def wrapper():
            start_time = time.time()
            func()
            stop_time = time.time()
            print("test函数的运行时长是: %s" % (stop_time - start_time))
            return 123   # 给wrapper函数加的返回值
        return wrapper
    
    @timer
    def test():
        time.sleep(2)
        print("test函数运行完毕")
        return "test函数返回好消息了"
    
    res = test()
    print(res)
    # 结果
    test函数运行完毕
    test函数的运行时长是: 2.0010011196136475
    123    # 返回的是123,
    
    # 上面运行返回的是123,而我们的需求是要返回“test函数返回好消息了”
    # 接着开,wrapper函数运行的是func函数,而func函数就是传入的test函数,所以把 func()赋值给变量 res呢?
    
    # 代码如下:
    def timer(func):
        def wrapper():
            start_time = time.time()
            res = func()    # 赋值给res
            stop_time = time.time()
            print("test函数的运行时长是: %s" % (stop_time - start_time))
            return res      # 返回res
        return wrapper
    
    @timer
    def test():
        time.sleep(2)
        print("test函数运行完毕")
        return "test函数返回好消息了"
    
    res = test()
    print(res)
    
    # 结果
    test函数运行完毕
    test函数的运行时长是: 2.0009818077087402
    test函数返回好消息了
    
    # 至此,给装饰器加上返回值就实现了。
    
    # 但是还有个问题,如果直接调用test呢?
    test()
    # 结果如下,为什么?暂时还不明白
    test函数运行完毕
    test函数的运行时长是: 2.0001914501190186

     为闭包加上参数

    # 需求:给test传入参数
    
    import time
    def timmer(func): #func=test1
        def wrapper(): 
            start_time=time.time()
            res=func() 
            stop_time = time.time()
            print('运行时间是%s' %(stop_time-start_time))
            return res
        return wrapper
    
    @timmer #test=timmer(test)
    def test(name,age):
        time.sleep(3)
        print('test函数运行完毕,名字是【%s】 年龄是【%s】' %(name,age))
        return '这是test的返回值'
    
    res=test('lixiao',age=18)  #就是在运行wrapper
    print(res)
    #结果:
    报错
    
    为什么呢?
    因为运行test就是在运行wrapper,而test传入了参数,wrapper里却没有接受参数,所以就报错了。那怎么改呢?
    1. 礼尚往来,你给我得接着呀,所有wrapper需要有参数接受test传来的值。
    2. wrapper 接受到了传来的值,wrapper函数体内,运行的res=func()实际上就是test(),所以,wrapper接受的参数也要传给func()才行。
    
    import time
    def timmer(func): #func=test1
        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
    
    @timmer #test=timmer(test)
    def test(name,age):
        time.sleep(3)
        print('test函数运行完毕,名字是【%s】 年龄是【%s】' %(name,age))
        return '这是test的返回值'
    
    
    #备注:
    wrapper(*args,**kwargs):表示的是可变长参数
    # *args:  代表把所有的位置参数接成列表
    # **kwargs: 代表可能用户传值的时候通过关键字传值,比如用户传的值是age =18, 或者{"age": 18}形式的, 就用**kwargs来接
    
    # 所以,*args,**kwargs :代表可变长参数,传多少参数,任意形式的都可以接受了
    
    @timmer
    def test1(name,age,gender):
        time.sleep(1)
        print('test1函数运行完毕,名字是【%s】 年龄是【%s】 性别【%s】' %(name,age,gender))
        return '这是test的返回值'
    
    test1('alex', 18, gender='male')

    闭包:补充解压序列 (很有用)

    # 需求:有个很长的列表,拿出第一个和最后一个值
    
    l = [1,2,4,5,6,123,34,1,6]
    
    方式1:索引的方式
    print(l[0])
    print(l[-1])
    
    
    方式2:解压序列方式
    
    l = [1,2,4,5,6,123,34,1,6]
    
    a, *_, c = l
    print(a)    # 1
    print(*_)   # 2 4 5 6 123 34 1
    print(c)    # 6
    
    # a 代表取第一个值
    # * 代表所有的值,下划线_表示不要了,当然下划线可以有任意字符替换, 如 *b, *d都可以
    # c 代表最后一个值了
    
    
    
    # 需求:a =1, b=2, 交换a,b的值
    a = 1
    b = 2
    
    a,b = b,a
    print(a)    # 2
    print(b)    # 1

    函数闭包为函数加上认证功能

    def auth_func(func):
        def wrapper(*args, **kwargs):
            username = input('用户名: ').strip()
            password = input("密码: ").strip()
            if username =="sb" and password == "123321":   # 用户是写死了
                res = func(*args, **kwargs)
                return res    # 切记要有返回值那
            else:
                print("用户名或密码错误")
        return wrapper
    
    @auth_func
    def index():
        print("欢迎来到京东")
    
    @auth_func
    def home():
        print("欢迎回家")
    
    @auth_func
    def shopping_box(name):
        print("%s的购物车里有%s,%s,%s" % (name, '娃娃', '电脑','手机'))
    
    index()
    home()
    shopping_box('韩梅梅')

     函数闭包模拟session

    上面为每个功能都加了验证功能,但是每次运行其他功能都需要重新登陆,这显然是不合理的,可以用session和cookie来解决,那怎么模拟解决这个问题呢?

    还有一个问题,就是用户是写死的,而实战中,我们的用户不可能只有一个,而且写死,所以这里也是需要改造的。

    # 用户一般都是存在数据库里,这里为了方便模拟,就把用户存储在一个列表里
    user_list = [
        {'name': 'sb', 'passwd': '123'},
        {'name': 'lhf', 'passwd': '123'},
        {'name': 'wpq', 'passwd': '123'},
        {'name': 'yh', 'passwd': '123'},
    ]
    
    current_dic = {'username':None, 'login':False}  # 模式当前登录状态是FALSE,未登录
    
    def auth_func(func):
        def wrapper(*args, **kwargs):
            # 在运行wrapper之前, 先判断是否有用户登录
            if current_dic['username'] and current_dic['login']: # 这个代表的是由用户登录且是true,然后直接运行func()
                res= func(*args, **kwargs)
                return res
            # 如果上面的判断不为True,则输入用户名和密码
            username = input("用户名:")
            password = input("密  码:")
            # 输完后应该从user_list里提取信息
            for user in user_list:
                # 然后判断输入的用户名和密码与user_list里的是否一致
                if username == user['name'] and password == user['passwd']:
                    # 如果有一致的, 要把当前状态改一下
                    current_dic['username'] = username  # 记录状态的字典里用户名 = 用户输入的用户名
                    current_dic['login'] = True         # 登陆状态改为true
                    res = func(*args, **kwargs)
                    return res    # 切记要有返回值那
            # 如果遍历了一遍,都没有匹配的
            else:
                print("用户名或密码错误")
        return wrapper
    
    @auth_func
    def index():
        print("欢迎来到京东")
    
    @auth_func
    def home():
        print("欢迎回家")
    
    @auth_func
    def shopping_box(name):
        print("%s的购物车里有%s,%s,%s" % (name, '娃娃', '电脑','手机'))
    
    index()
    home()
    shopping_box('韩梅梅')

    函数闭包带参数装饰器-----带参数的装饰器

    user_list=[
        {'name':'alex','passwd':'123'},
        {'name':'linhaifeng','passwd':'123'},
        {'name':'wupeiqi','passwd':'123'},
        {'name':'yuanhao','passwd':'123'},
    ]
    current_dic={'username':None,'login':False}
    
    def auth(auth_type='filedb'):
        def auth_func(func):
            def wrapper(*args,**kwargs):
                print('认证类型是',auth_type)
                if auth_type == 'filedb':
                    if current_dic['username'] and current_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']:
                            current_dic['username']=username
                            current_dic['login']=True
                            res = func(*args, **kwargs)
                            return res
                    else:
                        print('用户名或者密码错误')
                elif auth_type == 'ldap':
                    print('鬼才特么会玩')
                    res = func(*args, **kwargs)
                    return res
                else:
                    print('鬼才知道你用的什么认证方式')
                    res = func(*args, **kwargs)
                    return res
    
            return wrapper
        return auth_func
    
    @auth(auth_type='filedb') #auth_func=auth(auth_type='filedb')-->@auth_func 附加了一个auth_type  --->index=auth_func(index)
    def index():
        print('欢迎来到京东主页')
    
    @auth(auth_type='ldap')
    def home(name):
        print('欢迎回家%s' %name)
    #
    @auth(auth_type='sssssss')
    def shopping_car(name):
        print('%s的购物车里有[%s,%s,%s]' %(name,'奶茶','妹妹','娃娃'))
    
    # print('before-->',current_dic)
    index()
    # print('after--->',current_dic)
    home('产品经理')
    shopping_car('产品经理')
  • 相关阅读:
    SQL SERVER2005 的三种复制类型概述
    Autofac IoC容器基本实战【2】
    Autofac IoC容器基本使用步骤【1】
    在执行一行代码之前CLR做的68件事[The 68 things the CLR does before executing a single line of your code]
    .NET Entity Framework(EF)使用SqlQuery直接操作SQL查询语句或者执行过程
    使用 Entity Framework 7 进行 SQLite 的 CURD 操作
    NPOI导入导出EXCEL通用类,供参考,可直接使用在WinForm项目中
    Your Progress As A Programmer Is All Up To You
    为了效率而外包,不要因为懒惰外包
    10分钟看懂社群营销(上集)[转]
  • 原文地址:https://www.cnblogs.com/victorm/p/9203310.html
Copyright © 2020-2023  润新知