zoukankan      html  css  js  c++  java
  • 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)
      
  • 相关阅读:
    使用工具自动生成Linq类文件
    DateTime.MinValue和MaxValue引发的异常
    C# AD 验证登陆
    HttpWebRequest的GetResponse或GetRequestStream偶尔超时 + 总结各种超时死掉的可能和相应的解决办法
    清理sqlserver 2012 日志文件
    如何修改博客园插入代码的默认代码大小?
    hdu 1241:Oil Deposits(DFS)
    【2014年寒假日常记录表(2014.1.9—2.23,45天)】
    hdu 1016 Prime Ring Problem(DFS)
    蓝桥杯 历届试题 错误票据(水题,排序)
  • 原文地址:https://www.cnblogs.com/yaoqi17/p/11084589.html
Copyright © 2011-2022 走看看