python函数之闭包及装饰器

一、闭包

  1. 闭包的定义:

    • 闭包是嵌套在函数中的函数。

    • 闭包必须是内层函数对外层函数的变量(非全局变量)的引用。

      def make_average():
          li = []
          def average(price):
              li.append(price)
              total = sum(li)
              return total/len(li)
          return average
      avg = make_average()
      print(avg(100000)) # 100000.0
      print(avg(110000)) # 105000.0
      print(avg(120000)) # 110000.0
      

      1561097228948

      ​ 如上图当在函数嵌套时,第一层函数返回的是第二层函数的函数名,并且内层函数引用了第一层函数的变量的时候就形成了闭包。闭包函数的空间不会随着函数 的结束而消失,引用的变量被称为自由变量,也不会随函数的结束而消失。

  2. 如何判断闭包

    如果此函数拥有自由变量,那么就可以侧面证明其是否是闭包函数了

    # 对上个例子使用入下命令
    print(avg.__code__.co_freevars)
    # ('li',)
    

    通过以上命令可查看函数中是否存在自由变量

    # 拓展:查看其他变量名命令
    print(avg.__code__.co_varnames) # 查看局部变量
    # ('price', 'total')
    print(avg.__closure__ ) # 获取具体的自由变量
    # (<cell at 0x000001ACC81487F8: list object at 0x000001ACC83447C8>,)
    print(avg.__closure__[0].cell_contents) # 获取具体自由变量的值
    # [100000, 110000, 120000]
    
  3. 闭包的作用

    保存局部信息不被销毁,保证数据安全性

  4. 闭包的应用

    • 可以保存一些非全局变量但是不易被销毁和改变的数据
    • 装饰器的基础

二、装饰器

  1. 开放封闭原则

    • 软件面世时不可能将所有的功能都设计好,会定期进行更新迭代。对于软件之前的源码一般不会修改,对函数里面的代码以及函数的调用方式不会改变。

    • 封闭原则:不改变源码

    • 开放原则:更新增加一些额外的功能

      而装饰器完美的诠释了开放封闭原则

  2. 初识装饰器

    1. 定义:装饰器就是一个函数,他要装饰一个函数,在不改变原函数的源码及调用方式的前提下给其增加额外的功能

      • 有一个函数,现在要给其增加一个测试执行效率的功能

        def index():
            print('欢迎访问博客园')
        
      • 第一步:通过添加time模块进行测试

        import time
        def index():
            time.sleep(2)
            print('欢迎访问博客园首页')
        start_time = time.time()
        index()
        end_time = time.time()
        print(f'此函数的执行效率{end_time-start_time}')
        

        ​ 虽然这种方式完成了对此函数增加效率测试功能,但当需要对多个对象增加此项功能时,就会出现过多的代码重复

      • 第三步:通过能将功能封装在函数中来避免代码的重复

        import time
        def index():
            time.sleep(2)
            print('欢迎访问博客园首页')
        def test_time(x):
            start_time = time.time()
            x()
            end_time = time.time()
            print(f'此函数的执行效率{end_time- start_time}')
        test_time(index)
        

        ​ 虽然解决了代码重复问题,并且在不改变源码的前提下完成了给函数添加了功能,但改变了函数的调用方式

      • 第四步:通过闭包来达到使原函数的调用方式不变

        import time
        def index():
            time.sleep(2)
            print('欢迎访问博客园首页')
        def test_time(x):
            def inner():
                start_time = time.time()
                x()
                end_time = time.time()
                print(f'此函数的执行效率{start_time-end_time}')
            return inner
        ret = test_time(index)
        ret()
        

        ​ 此时虽然进行了闭包但是函数调用方式还是改变了

      • 第五步:将函数调用这一步直接赋给原函数的函数名

        import time
        def index():
            time.sleep(2)
            print('欢迎访问博客园首页')
        def test_time(x):
            def inner():
                start_time = time.time()
                x()
                end_time = time.time()
                print(f'此函数的执行效率{start_time-end_time}')
            return inner
        index = test_time(index)
        index()
        

        ​ 这样就完成了在不改变原函数源码以及调用方式的前提下,给函数增加了一项功能

    2. 通过语法糖加装装饰器

      import time
      def test_time(x):
          def inner():
              start_time = time.time()
              x()
              end_time = time.time()
              print(f'此函数的执行效率{start_time-end_time}')
          return inner
      @test_time
      def index():
          time.sleep(2)
          print('欢迎访问博客园首页')
      index()
      
  3. 被装饰的函数带返回值

    ​ 当被装饰函数带返回值时,由于装饰器实际执行的时inner函数,而inner函数并没有返回值,因此当被装饰函数带返回值时,直接用上述装饰器的话得到的返回值就是None。

    ​ 所以应对装饰器进行升级,对其增加返回值并且返回的应该是index函数的返回值

    import time
    def test_time(x):
        def inner():
            start_time = time.time()
            ret = x()
            end_time = time.time()
            print(f'此函数的执行效率{start_time-end_time}')
            return ret
        return inner
    @test_time
    def index():
        time.sleep(2)
        print('欢迎访问博客园首页')
        return 666
    index()     
    
  4. 被装饰的函数带参数

    ​ 当函数带参数时,同上面的原因,由于inner函数缺少形参函数执行时会报错,要使函数执行就需要给inner函数增加形参

    import time
    def test_time(x):
        def inner(*args,**kwargs):
            start_time =time.time()
            ret = x(*args,**kwargs)
            end_time = time.time()
            print(f'此函数的执行效率{start_time-end_time}')
            return ret
        return inner
    @test_time
    def index(1,2,3):
        time.sleep(2)
        print('欢迎访问博客园首页')
        return 666
    
  5. 标准版装饰器

    装饰器的格式:

    def warpper(f):
        def inner(*args,**kwargs):
            '''被装饰函数之前的操作'''
            ret = f(*args,**kwargs)
            '''被装饰函数之后的操作'''
            return ret
        return inner
    
  6. 带参数的装饰器

    ​ 举例说明,抖音:绑定的是微信账号密码。 qq:绑定的是qq的账号密码。 你现在要完成的就是你的装饰器要分情况去判断账号和密码,不同的函数用的账号和密码来源不同。

    ​ 由于之前写的装饰器只能接受一个参数就是函数名,所以现在要写一个可以接受参数的装饰器。

    def chioce(n):
        def wrapper(f):
            def inner(*args,**kwargs):
                if n== 'qq':
                    user_name = input('请输入用户名:').strip()
                    password = input('请输入密码:').strip()
                    with open('qq',encoding = 'utf-8') as f1:
                        for i in f1:
                            user, pwd = i.strip().split('|')
                            if user_name == user and pwd == password:
                                ret = f(*args,**kwargs)
                                return ret
                        print('用户名密码错误')
                 elif n == 'tiktok':
                    user_name = input('请输入用户名:').strip()
                    password = input('请输入密码:').strip()
                    with open('qq',encoding = 'utf-8') as f1:
                        for i in f1:
                            user, pwd = i.strip().split('|')
                            if user_name == user and pwd == password:
                                ret = f(*args,**kwargs)
                                return ret
                        print('用户名密码错误')
            return inner
        return wrapper
    @chioce('qq')
    def qq():
        print('成功访问qq')
    @chioce('tiktok')
    def tiktok():
        print('成功访问抖音')
    qq()
    tiktok()
    

    由于中间有一部分是重叠代码可进行简化:

    def chioce(n):
        def wrapper(f):
            def inner(*args,**kwargs):
                user_name = input('请输入用户名:').strip()
                password = input('请输入密码:').strip()
                with open(n,encoding = 'utf-8') as f1:
                    for i in f1:
                        user, pwd = i.strip().split('|')
                        if user_name == user and pwd == password:
                            ret = f(*args,**kwargs)
                            return ret
                    else:
                        print('用户名密码错误')
            return inner
        return wrapper
    @chioce('qq')
    def qq():
        print('成功访问qq')
    @chioce('tiktok')
    def tiktok():
        print('成功访问抖音')
    qq()
    tiktok()
    

    将文件名用传进去的参数n代替,可通过n直接判断调用的使哪个函数的文件

  7. 多个装饰器装饰一个函数

    有如下代码分析最后打印的结果:

    def wrapper1(func1): # func1 == f函数
        def inner1():
            print('wrapper1,before func') # 2
            func1() # 3
            print('wrapper1,after func') # 4
        return inner1
    def wrapper2(fun2): # func2 == inner1
        def inner2():
            print('wrapper2,before func') # 1
            func2() # func2 == inner1
            print('wrapper2,after func') # 5
        return inner2
    @wrapper2	# f = wrapper2(f)中里面的f==inner1 外面的f==inner2
    @wrapper1	# f = wrapper1(f)中里面的f==func1 外面的f==inner1
    def f():
        print('in f')
    f()
    结果:
    wrapper2,before func
    wrapper1,before func
    in f
    wrapper1,after func
    wrapper2,after func
    

  8. 递归函数

    递归函数:函数或者其他代码都可以解决递归解决的问题,但是递归在某些时候能出奇制胜的效果,人理解函数,神理解递归。其实递归就是函数本身调用他自己

    举例:

    def age(n):
        if n == 1:
            return 18
        else:
            return age(n-1)+2
    print(age(4))
    结果:
    24 # 18+2+2+2
    

    可通过sys.setrecursionlimit查看电脑递归次数上限:

    import sys
    import sys
    print(sys.setrecursionlimit(10000000))
    def func(n):
        print(n)
        n += 1
        print(666)
        func(n)
    func(1)
    

    将如下列表中的每项元素打印出来:

    l1 = [1, 3, 5, ['太白','元宝', 34, [33, 55, [11,33]]], [77, 88],66]
    def func(n):
        for i in n:
            if type(i) == list:
                fun(i)
             else:
                print(i)
    func(l1)
    
原文地址:https://www.cnblogs.com/yaoqi17/p/11084589.html