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

目录

一、装饰器的概念和意义

1、装饰器的概念

2、函数即变量

3、高阶函数

4、嵌套函数

5、编写装饰器

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

1、被装饰函数带参数

2、装饰器本身带参数

3、被装饰函数带返回值

三、函数闭包

四、闭包和装饰器的区别


一、装饰器的概念和意义

1、装饰器的概念

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

2、函数即变量

  • 函数既可以直接被调用,也可以作为变量进行赋值。

  • 函数名跟变量名一样,只是一个变量的标识符,它指向函数定义对应的内存地址。

  • 例如:

    • boo赋值给a后,a也能调用boo函数。
    python 复制代码
    def boo():
        print("in boo")
    a=boo
    boo()  # in boo
    a()  # in boo
  • 任何变量名都是指向变量值的内存地址,如果把存放一个变量值的空间看成是一间屋子的话,那么这间屋子里存放的就是变量的值,而变量名则是屋子上的门牌号。

  • 对于函数也是一样的道理,函数的本质是一串字符串,这串字符串会保存在内存空间中,函数名是指向这个内存空间的地址,也相当于一个门牌号。

  • 在函数定义中去调用其他函数时,并不会立即调用该函数。

  • 例如:
    *

    python 复制代码
    def foo():
        print("in foo")
        boo()
    def boo():
        print("in boo")
    foo()  # 不会报错
  • 在执行一个调用了其他函数的函数时,如果在内存中还没有找到被调用函数的定义,则程序会报错。

  • 例如:
    *

    python 复制代码
    def foo():
        print("in foo")
        boo()
    foo()  # 会报错
    def boo():
        print("in boo")

3、高阶函数

  • 符合下列条件之一的函数就是高阶函数:

    • 接受函数名作为形参
    • 返回值中包含函数名
  • 例如:
    *

    python 复制代码
    def foo():
        print("in foo")
    def gf(func):  # 高阶函数
        print(func)  # 运行foo函数之前附加的功能语句
        func()
    gf(foo)
  • 尝试使用高阶函数统计任意函数运行时间。

    python 复制代码
    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的调用方式,不满足装饰器的第二个原则,说明其不是一个真正的装饰器
    • 【注】这并不是一个装饰器。该方式确实没有改变函数的源码,但是却改变了函数的调用方式。
  • 再尝试不改变调用方式能不能实现该功能。
    *

    python 复制代码
    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关键字定义在另一个函数中的函数叫嵌套函数。

  • 例如:

    python 复制代码
    def foo():
        print('in foo')
        def boo():
            print('in boo')
  • 尝试加入嵌套函数统计任意函数运行时间。
    *

    python 复制代码
    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、编写装饰器

  • 基本套路:

    • 定义一个接受函数名作为参数的高阶函数。
    • 在高阶函数中定义一个嵌套函数,在该嵌套函数中封装想要添加的功能代码,调用作为参数传入的函数名,返回嵌套函数的函数名。
  • 例如:编写一个装饰器,要求使用该装饰器能够统计任意函数运行时间。

    • 我们已经在上面实现了装饰器的基本原理,现在只需要修改装饰器的表示方式即可。
    python 复制代码
    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、被装饰函数带参数

  • 当被装饰函数有一个参数时,需要为嵌套函数也添加一个参数,代码如下:
    *

    python 复制代码
    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)
  • 如果有多个参数呢,是不是每次都需要修改嵌套函数呢?我们可以为嵌套函数设定一个不定量参数。
    *

    python 复制代码
    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)就是不定量参数的表示。
  • 模板:
    *

    python 复制代码
    # 被装饰函数带有参数或不带参数
    def deco(func):
        def inner(*args,**kwargs):
            # 包含所有要附加的功能
            func(*args,**kwargs)
        return inner

2、装饰器本身带参数

  • 装饰器本身带参数时,需要再添加一层嵌套函数。
    *

    python 复制代码
    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)  
  • 模板:
    *

    python 复制代码
    # 装饰器本身带参数
    def deco1(parma):  # param是装饰器本身的参数
        def outer(func):  # 以被装饰的函数名作为参数
            def inner(*args,**kwargs):
                # 包含所有要附加的功能
                func(*args,**kwargs)
            return inner
        return outer

3、被装饰函数带返回值

  • 被装饰函数带返回值时,需要在嵌套函数中返回它的值。
    *

    python 复制代码
    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)  
  • 模板:
    *

    python 复制代码
    # 被装饰函数带返回值
    def deco2(parma): # param是装饰器本身的参数
        def outer(func): # 以被装饰的函数名作为参数
            def inner(*args,**kwargs):
                #包含所有要附加的功能
                result = func(*args,**kwargs)  # 接收到被装饰函数的返回值
                return result  # 返回被装饰函数的返回值
            return inner
        return outer

三、函数闭包

  • 我们先来看一下什么情况需要使用闭包。
    *

    python 复制代码
    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的影响。这个时候我们就需要使用到闭包。

    • 我们对代码稍作修改。
      *

      python 复制代码
      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、闭包的目的是保存函数运行环境和局部变量值 |

相关推荐
炼丹师小米5 分钟前
Ubuntu24.04.1系统下VideoMamba环境配置
python·环境配置·videomamba
GFCGUO11 分钟前
ubuntu18.04运行OpenPCDet出现的问题
linux·python·学习·ubuntu·conda·pip
快乐就好ya36 分钟前
Java多线程
java·开发语言
CS_GaoMing1 小时前
Centos7 JDK 多版本管理与 Maven 构建问题和注意!
java·开发语言·maven·centos7·java多版本
985小水博一枚呀2 小时前
【深度学习基础模型】神经图灵机(Neural Turing Machines, NTM)详细理解并附实现代码。
人工智能·python·rnn·深度学习·lstm·ntm
2401_858120532 小时前
Spring Boot框架下的大学生就业招聘平台
java·开发语言
转调2 小时前
每日一练:地下城游戏
开发语言·c++·算法·leetcode
Java探秘者2 小时前
Maven下载、安装与环境配置详解:从零开始搭建高效Java开发环境
java·开发语言·数据库·spring boot·spring cloud·maven·idea
2303_812044463 小时前
Bean,看到P188没看了与maven
java·开发语言
秋夫人3 小时前
idea 同一个项目不同模块如何设置不同的jdk版本
java·开发语言·intellij-idea