• Python 装饰器、嵌套函数、高阶函数


    装饰器

    装饰器 = 高阶函数 + 嵌套函数

    定义

    用来装饰其他函数的,即为其他函数添加特定功能的函数,即装饰器本质也是一个函

    装饰器函数的两个基本原则

    • 装饰器不能修改被装饰函数的源码
    • 装饰器不能修改被装饰函数的调用方式

    函数即变量

    函数即变量的思想:即函数也可以像变量一样赋值使用,如

    def foo():
        print('in foo')
    
    a = foo  # 把函数赋值给一个变量
    a()  # 调用这个变量,即和调用函数一样的效果

    高阶函数

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

    (1)接收一个函数作为形参

    (2)返回一个函数(return 函数)

    高阶函数的两个条件对编写装饰器的意义:

    (1)接收函数作为形参 -> 可以不改变被装饰函数的代码的前提下增加功能

    1. def foo():
    2. print('in foo')
    3. # gf 函数接收一个函数作为变量,故 gf 是一个高阶函数
    4. def gf(func):
    5. # func = foo
    6. # 等同于把一个函数赋值给一个变量 func,然后通过变量 func 调用函数
    7. func() # 通过变量调用函数
    8. # 高阶函数除了可以调用 foo 函数,即执行 foo 函数功能之外,可以附加执行一些功能,如打印函数地址
    9. # 这接近于装饰器的功能:在不改变别的函数源码的前提下给其添加附加功能
    10. # 但高阶函数不是装饰器,因为满足条件一:不改变被装饰函数的源码,但不满足条件二:不改变被装饰函数的调用方式
    11. print(func)
    12. gf(foo) # 改变了函数 foo 的调用方式

    (2)返回一个函数(return 函数) -> 可以不改变被装饰函数的调用方式

    1. def foo():
    2. print('in foo')
    3. # gf 函数接收一个函数作为变量,故 gf 是一个高阶函数
    4. def gf(func):
    5. # func = foo
    6. # 等同于把一个函数赋值给一个变量 func,然后通过变量 func 调用函数
    7. return func
    8. # 可以不改变 foo 函数的调用方式
    9. foo = gf(foo)
    10. foo()

    但是可以看出高阶函数不能同时满足两个条件,所以高阶函数不是真正的装饰器

    嵌套函数

    通过 def 关键字定义在另一个函数中的函数叫嵌套函数,如

    1. def foo():
    2. print('in foo')
    3. def boo():
    4. print('in boo')

    装饰器实现

    装饰器 = 高阶函数 + 嵌套函数,所以可以得到如下代码

    1. import time
    2. def timer(func):
    3. # func = foo
    4. # 接收一个函数,可以在不改变函数源码的情况下为其增加附加功能
    5. def gf():
    6. start_time = time.time()
    7. func() # => foo()
    8. end_time = time.time()
    9. print('func 运行时间为:', end_time - start_time)
    10. # 返回一个函数,可以不改变被嵌套函数的调用方式
    11. return gf
    12. def foo():
    13. time.sleep(2)
    14. print('in foo')
    15. foo = timer(foo)
    16. foo() # 函数调用方式不变,又多了打印其运行时间的附加功能

    @的形式是Python 提供的装饰器的语法糖,等价于 foo = timer(foo)

    1. import time
    2. def timer(func):
    3. # func = foo
    4. # 接收一个函数,可以在不改变函数源码的情况下为其增加附加功能
    5. def gf():
    6. start_time = time.time()
    7. func() # => foo()
    8. end_time = time.time()
    9. print('func 运行时间为:', end_time - start_time)
    10. # 返回一个函数,可以不改变被嵌套函数的调用方式
    11. return gf
    12. @timer # => foo = timer(foo)
    13. def foo():
    14. time.sleep(2)
    15. print('in foo')
    16. # foo = timer(foo)
    17. foo() # 函数调用方式不变,又多了打印其运行时间的附加功能

    装饰器的运行过程

    可以通过给上述的每一行代码加上断点,运行 debug 就可以看到装饰器的运行过程

    装饰器编写的基本套路

    1. 定义一个接收函数的高阶函数
    2. 在高阶函数中定义一个嵌套函数,在该嵌套函数中:
      1. 封装想要添加的功能代码
      2. 调用作为参数传入的函数名
    3. 高阶函数返回该嵌套函数

    常见的几种装饰器类型

    被装饰函数带参数

    带一个参数

    1. import time
    2. def timer(func):
    3. # func = foo
    4. # 接收一个函数,可以在不改变函数源码的情况下为其增加附加功能
    5. def gf(name):
    6. start_time = time.time()
    7. func(name) # => foo(name)
    8. end_time = time.time()
    9. print('func 运行时间为:', end_time - start_time)
    10. # 返回一个函数,可以不改变被嵌套函数的调用方式
    11. return gf
    12. @timer # => foo = timer(foo) => foo = gf
    13. def foo(name):
    14. time.sleep(2)
    15. print('in foo', name)
    16. # foo = timer(foo) # => foo = gf
    17. foo('python') # => gf('python')

    带多个参数

    1. import time
    2. def timer(func):
    3. # func = foo
    4. # 接收一个函数,可以在不改变函数源码的情况下为其增加附加功能
    5. # *args, **kwargs 可以接收任意的参数
    6. def gf(*args, **kwargs):
    7. start_time = time.time()
    8. func(*args, **kwargs) # => foo(*args, **kwargs)
    9. end_time = time.time()
    10. print('func 运行时间为:', end_time - start_time)
    11. # 返回一个函数,可以不改变被嵌套函数的调用方式
    12. return gf
    13. @timer # => foo = timer(foo) => foo = gf
    14. def foo(name, age):
    15. time.sleep(2)
    16. print('in foo', name, age)
    17. # foo = timer(foo) # => foo = gf
    18. foo('python', 18) # => gf('python', 18)

    装饰器本身带参数

    1. import time
    2. def timer(timer_type):
    3. print(timer_type)
    4. def outer(func):
    5. # func = foo
    6. # *args, **kwargs 可以接收任意的参数
    7. def gf(*args, **kwargs):
    8. start_time = time.time()
    9. func(*args, **kwargs) # => foo(*args, **kwargs)
    10. end_time = time.time()
    11. print('func 运行时间为:', end_time - start_time)
    12. # 返回一个函数,可以不改变被嵌套函数的调用方式
    13. return gf
    14. return outer
    15. # 此时 timer('装饰器参数') 带有小括号,表示已经调用了 timer 函数,那么得到了 timer 函数的返回值 outer
    16. # 所以 @timer('装饰器参数') 已经变成了 @outer
    17. @timer('装饰器参数') # => @outer
    18. def foo(name, age):
    19. time.sleep(2)
    20. print('in foo', name, age)
    21. # foo = timer('装饰器参数')(foo) # => foo = outer(foo) # => foo = gf
    22. foo('python', 18) # => gf('python', 18)
    23. # 提示:把每一行代码打断点调试可以更好的理解装饰器的运行过程

    被装饰函数带返回值

    1. import time
    2. def timer(timer_type):
    3. print(timer_type)
    4. def outer(func):
    5. # func = foo
    6. # *args, **kwargs 可以接收任意的参数
    7. def gf(*args, **kwargs):
    8. start_time = time.time()
    9. res = func(*args, **kwargs) # => foo(*args, **kwargs)
    10. end_time = time.time()
    11. print('func 运行时间为:', end_time - start_time)
    12. return res
    13. # 返回一个函数,可以不改变被嵌套函数的调用方式
    14. return gf
    15. return outer
    16. # 此时 timer('装饰器参数') 带有小括号,表示已经调用了 timer 函数,那么得到了 timer 函数的返回值 outer
    17. # 所以 @timer('装饰器参数') 已经变成了 @outer
    18. @timer('装饰器参数') # => @outer
    19. def foo(name, age):
    20. time.sleep(2)
    21. print('in foo', name, age)
    22. return 'foo 返回了'
    23. # foo = timer('装饰器参数')(foo) # => foo = outer(foo) # => foo = gf
    24. res = foo('python', 18) # => gf('python', 18)
    25. print(res)
    26. # 提示:把每一行代码打断点调试可以更好的理解装饰器的运行过程

    一个函数使用多个装饰器

    1. # 多个装饰器可以放在一个函数上
    2. def add(func):
    3. def inner1():
    4. print("inner1...")
    5. x = func()
    6. return x + 1
    7. return inner1
    8. def cube(func):
    9. def inner2():
    10. print("inner2...")
    11. x = func()
    12. return x * x * x
    13. return inner2
    14. def square(func):
    15. def innner3():
    16. print("inner3...")
    17. x = func()
    18. return x * x
    19. return innner3
    20. @add
    21. @cube
    22. @square
    23. def test():
    24. return 2
    25. # 从结果判断执行顺序:square -> cube -> add
    26. # test 函数传递给 square 函数
    27. # square 函数中的 innner3 函数传递给 cube 函数
    28. # cube 函数中的 innner2 传递给 add 函数
    29. # add 函数中的 innner1 传递给 test
    30. print(test()) # 65

  • 相关阅读:
    Vue2电商前台项目——项目的初始化及搭建
    借道元宇宙 一汽-大众揽巡打造沉浸式上市体验
    [译]2023年 Web Component 现状
    C++模板介绍
    Docker安装Apollo
    面试官:谈谈 Go GC 算法
    Vuex基础使用存取值+异步&请求后台
    云原生数据库前世今生
    GPT-4充当“规划师、审计师”,颠覆性双层文生图表模型
    win10系统同时安装 vue2和vue3
  • 原文地址:https://blog.csdn.net/2301_77659011/article/details/132824569