Python进阶教学——装饰器与闭包

目录

一、装饰器的概念和意义

1、装饰器的概念

2、函数即变量

3、高阶函数

4、嵌套函数

5、编写装饰器

二、装饰器的常见类型和编写

1、被装饰函数带参数

2、装饰器本身带参数

3、被装饰函数带返回值

三、函数闭包

四、闭包和装饰器的区别


一、装饰器的概念和意义

1、装饰器的概念

  • 装饰器:用来装饰其他函数,即为其他函数添加特定功能的函数。
  • 装饰器 = 高阶函数 + 嵌套函数
  • 装饰器函数的两个基本原则:
    • 装饰器不能修改被装饰函数的源码。
    • 装饰器不能修改被装饰函数的调用方式。
  • 学习装饰器之前必须掌握的概念:函数即变量、高阶函数、嵌套函数。

2、函数即变量

  • 函数既可以直接被调用,也可以作为变量进行赋值。
  • 函数名跟变量名一样,只是一个变量的标识符,它指向函数定义对应的内存地址。
  • 例如: 
    • boo赋值给a后,a也能调用boo函数。 
    • def boo():
          print("in boo")
      a=boo
      boo()  # in boo
      a()  # in boo
  • 任何变量名都是指向变量值的内存地址,如果把存放一个变量值的空间看成是一间屋子的话,那么这间屋子里存放的就是变量的值,而变量名则是屋子上的门牌号。
  • 对于函数也是一样的道理,函数的本质是一串字符串,这串字符串会保存在内存空间中,函数名是指向这个内存空间的地址,也相当于一个门牌号。
  • 在函数定义中去调用其他函数时,并不会立即调用该函数。
  • 例如:
    • def foo():
          print("in foo")
          boo()
      def boo():
          print("in boo")
      foo()  # 不会报错
  • 在执行一个调用了其他函数的函数时,如果在内存中还没有找到被调用函数的定义,则程序会报错。
  • 例如:
    • def foo():
          print("in foo")
          boo()
      foo()  # 会报错
      def boo():
          print("in boo")
      
      

3、高阶函数

  • 符合下列条件之一的函数就是高阶函数:
    • 接受函数名作为形参
    • 返回值中包含函数名
  • 例如:
    • def foo():
          print("in foo")
      def gf(func):  # 高阶函数
          print(func)  # 运行foo函数之前附加的功能语句
          func()
      gf(foo)
  • 尝试使用高阶函数统计任意函数运行时间。

    • import time
      def foo():
          print('in foo')
      def gf(func):
          start_time=time.time()  # 记录函数开始运行的时间
          func()
          end_time=time.time()
          print('运行func的时间为:',end_time-start_time)
      gf(foo)  # 改变了foo的调用方式,不满足装饰器的第二个原则,说明其不是一个真正的装饰器
    • 【注】这并不是一个装饰器。该方式确实没有改变函数的源码,但是却改变了函数的调用方式。 
  • 再尝试不改变调用方式能不能实现该功能。
    • import time
      def foo():
          print('in foo')
      def gf(func):
          start_time=time.time()
          return func
          end_time=time.time()
          print('运行func的时间为':end_time-start_time)
      foo=gf(foo)
      foo()
    • 【注】该方式虽然没有改变函数的调用方式,但是函数在return时就已经结束了,不能实现计时的功能。
  • 高阶函数的两个条件对编写装饰器的意义:
    • 接受函数名作为形参(不改变被装饰函数的代码的前提下增加功能)
    • 返回值中包含函数名(不改变被装饰函数的调用方式)

4、嵌套函数

  • 通过def关键字定义在另一个函数中的函数叫嵌套函数。
  • 例如:
    def foo():
        print('in foo')
        def boo():
            print('in boo')
  • 尝试加入嵌套函数统计任意函数运行时间。
    • import time
      def foo():
          print('in foo')
      def timer(func):
          def gf():
              start_time=time.time()  # 记录函数开始运行的时间
              func()
              end_time=time.time()
              print('运行func的时间为:',end_time-start_time)
          return gf
      foo=timer(foo)
      foo()  # 实际调用的是gf函数
    • 此时,函数的源码以及调用方式都没有改变。

5、编写装饰器

  • 基本套路:
    • 定义一个接受函数名作为参数的高阶函数。
    • 在高阶函数中定义一个嵌套函数,在该嵌套函数中封装想要添加的功能代码,调用作为参数传入的函数名,返回嵌套函数的函数名。
  • 例如:编写一个装饰器,要求使用该装饰器能够统计任意函数运行时间。
    • 我们已经在上面实现了装饰器的基本原理,现在只需要修改装饰器的表示方式即可。
    • import time
      def timer(func):
          def gf():
              start_time=time.time()  # 记录函数开始运行的时间
              func()
              end_time=time.time()
              print('运行func的时间为:',end_time-start_time)
          return gf
      @timer  # foo=timer(foo),Python的语法糖,一种语法简化方式
      def foo():
          print('in foo')
      foo()
  • @timer就是一个装饰器。

二、装饰器的常见类型和编写

1、被装饰函数带参数

  • 当被装饰函数有一个参数时,需要为嵌套函数也添加一个参数,代码如下:
    • import time
      def timer(func):
          def gf(name):
              start_time=time.time()
              func(name)
              end_time=time.time()
              print('运行func的时间为:',end_time-start_time)
          return gf
      @timer  # foo=timer(foo)
      def foo(name):
          print('in foo',name)
      #foo=gf()
      foo("hhh")  # gf(name)
  • 如果有多个参数呢,是不是每次都需要修改嵌套函数呢?我们可以为嵌套函数设定一个不定量参数。 
    • import time
      def timer(func):
          def gf(*args,**kwargs):  # 通过提供不定量参数来自适应被装饰函数的参数
              start_time=time.time()
              func(*args,**kwargs)  # 不定量参数
              end_time=time.time()
              print('运行func的时间为:',end_time-start_time)
          return gf
      @timer  # foo=timer(foo)
      def foo(name,age):
          print('in foo',name,age)
      #foo=gf()
      foo("hhh",22)  # gf(*args,**kwargs)  
    •  【注】(*args,**kwargs)就是不定量参数的表示。
  • 模板:
    • # 被装饰函数带有参数或不带参数
      def deco(func):
          def inner(*args,**kwargs):
              # 包含所有要附加的功能
              func(*args,**kwargs)
          return inner

2、装饰器本身带参数

  • 装饰器本身带参数时,需要再添加一层嵌套函数。
    • import time
      def timer(timer_type):
          print(timer_type)
          def outer(func):  # 加入一层嵌套函数,并且接受被装饰函数名作为参数
              def inner(*args,**kwargs): 
                  start_time=time.time()
                  func(*args,**kwargs)
                  end_time=time.time()
                  print('运行func的时间为:',end_time-start_time)
              return inner
          return outer
      @timer(timer_type='minites')  # foo=timer(timer_type='minites')(foo)
      def foo(name,age):
          print('in foo',name,age)
      foo("hhh",22)  # inner(*args,**kwargs)  
      
  • 模板:
    • # 装饰器本身带参数
      def deco1(parma):  # param是装饰器本身的参数
          def outer(func):  # 以被装饰的函数名作为参数
              def inner(*args,**kwargs):
                  # 包含所有要附加的功能
                  func(*args,**kwargs)
              return inner
          return outer

3、被装饰函数带返回值

  • 被装饰函数带返回值时,需要在嵌套函数中返回它的值。
    • import time
      def timer(timer_type):
          print(timer_type)
          def outer(func):
              def inner(*args,**kwargs): 
                  start_time=time.time()
                  res=func(*args,**kwargs)  # 保存返回值
                  end_time=time.time()
                  print('运行func的时间为:',end_time-start_time)
                  return res   # 返回
              return inner
          return outer
      @timer(timer_type='minites')  # foo=timer(timer_type='minites')(foo)
      def foo(name,age):
          print('in foo',name,age)
          return name  # 被装饰函数带有返回值
      print(foo("hhh",22))  # inner(*args,**kwargs)  
  • 模板:
    • # 被装饰函数带返回值
      def deco2(parma): # param是装饰器本身的参数
          def outer(func): # 以被装饰的函数名作为参数
              def inner(*args,**kwargs):
                  #包含所有要附加的功能
                  result = func(*args,**kwargs)  # 接收到被装饰函数的返回值
                  return result  # 返回被装饰函数的返回值
              return inner
          return outer

三、函数闭包

  • 我们先来看一下什么情况需要使用闭包。
    • func_list = []
      for i in range(3):  # i=0,1,2
          def myfunc(a):
              return i+a  # 会受外部改变的影响
          func_list.append(myfunc)
      for f in func_list:
          print(f(1))
      # 预估结果
      # 1,2,3
      # 实际结果
      # 3,3,3
    • 可以看到,我们想到存储myfunc函数的运行环境,但是它的运行环境会受到外部变量i的影响。这个时候我们就需要使用到闭包。 
    • 我们对代码稍作修改。
      • func_list = []
        for i in range(3):  # i=0,1,2
            def deco(i):  # 接收i作为参数
                def myfunc(a):
                    return i+a  #此时i为myfunc的自由变量
                return myfunc  # 返回myfunc函数
            func_list.append(deco(i))
        for f in func_list:
            print(f(1))
        # 预估结果
        # 1,2,3
        # 实际结果
        # 1,2,3
  •  闭包的作用:
    • 可以用来在一个函数与一组私有变量之间创建关联关系。在给定函数被多次调用的过程中,这些私有变量能够保持其持久性(保存运行环境与变量的状态)。
  • 闭包的特征:
    • 必须要有函数的嵌套,而且外层函数必须返回内层函数。外层函数相当于给内层函数提供了一个包装起来的运行环境,在这个包装的运行环境里面,内层函数可完全自己掌握自由变量的值。
    • 内层函数一定要用到外层函数中定义的自由变量。

四、闭包和装饰器的区别

  • 装饰器 闭包
    相同的 1、都是函数的嵌套,分为外层函数和内层函数,而且外层函数要返回内层函数
    2、代码实现的逻辑大同小异
    3、二者都可以实现增加额外功能的目的
    不同点

    1、外层函数成为装饰器

    2、装饰器的外层函数主要是提供被装饰函数的引用

    3、装饰器的外层函数不一定要提供变量
    4、装饰器的目的:为被装饰函数提供额外的功能

    5、从形式上看,闭包是装饰器的子集

    1、外层函数称为闭包

    2、闭包的外层函数主要是为了提供自由变量
    3、闭包的外层函数必须提供自由变量,否则闭包无意义

    4、闭包的目的是保存函数运行环境和局部变量值

你可能感兴趣的:(Python,python,开发语言)