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


    目录

    一、装饰器的概念和意义

    1、装饰器的概念

    2、函数即变量

    3、高阶函数

    4、嵌套函数

    5、编写装饰器

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

    1、被装饰函数带参数

    2、装饰器本身带参数

    3、被装饰函数带返回值

    三、函数闭包

    四、闭包和装饰器的区别


    一、装饰器的概念和意义

    1、装饰器的概念

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

    2、函数即变量

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

    3、高阶函数

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

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

    4、嵌套函数

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

    5、编写装饰器

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

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

    1、被装饰函数带参数

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

    2、装饰器本身带参数

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

    3、被装饰函数带返回值

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

    三、函数闭包

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

    四、闭包和装饰器的区别

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

      1、外层函数成为装饰器

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

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

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

      1、外层函数称为闭包

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

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

  • 相关阅读:
    Keil 5 或者Keil 4自定义主题颜色100%成功
    c++(25)STL:类型转换、异常机制
    vivado 3D RAM Inference
    SpringBoot读取yml配置文件
    基于Python的自然语言处理项目 ChatTTS 推荐
    华为OD机试 - 跳格子游戏 - 深度优先搜索dfs算法(Java 2023 B卷 200分)
    Huggingface transformers 里的模型加载的两种方式的写法
    【Note】二叉树的遍历
    快捷方式变白解决方法
    IllegalStateException: FragmentManager is already executing transactions
  • 原文地址:https://blog.csdn.net/weixin_45100742/article/details/132800697