参考:通俗讲解python装饰器 :::info 通过闭包来实现装饰器,函数作为外层函数的传入参数,然后在内层函数中运行、附加功能,随后把内层函数作为结果返回 :::

是什么

顾名思义,从字面意思就可以理解,它是用来”装饰”Python的工具,使得代码更具有Python简洁的风格。换句话说,它是一种函数的函数,因为装饰器传入的参数就是一个函数,然后通过实现各种功能来对这个函数的功能进行增强。

为何使用

前面提到了,装饰器是通过某种方式来增强函数的功能。当然,我们可以通过很多方式来增强函数的功能,只是装饰器有一个无法替代的优势—简洁。
你只需要在每个函数上方加一个@就可以对这个函数进行增强。

何时使用

装饰器最大的优势是用于解决重复性的操作,其主要使用的场景有如下几个:

  • 计算函数运行时间
  • 给函数打日志
  • 类型检查

当然,如果遇到其他重复操作的场景也可以类比使用装饰器。

简单示例

  • 如果你要对多个函数进行统计运行时间,不使用装饰器会是这样的:在每个函数里都需要获取开始时间start、结束时间end、计算耗费时间cost_time、加上一个输出语句。
  • 而如果使用装饰器,通过编写一个统计时间的装饰器run_time,函数的作为装饰器的参数,然后返回一个统计时间的函数wrapper,这就是装饰器的写法,用专业属于来说这叫闭包,简单来说就是函数内嵌套函数。然后再每个函数上面加上@run_time来调用这个装饰器对不同的函数进行统计时间。

统计时间这4行代码是重复的,一个函数需要4行,如果100个函数就需要400行,而使用装饰器,只需要几行代码实现一个装饰器,然后每个函数前面加一句命令即可

  1. def run_time(func):
  2. def wrapper():
  3. start = time()
  4. func() # 函数在这里运行
  5. end = time()
  6. cost_time = end - start
  7. print("func three run time {}".format(cost_time))
  8. return wrapper
  9. @run_time
  10. def fun_one():
  11. sleep(1)
  12. @run_time
  13. def fun_two():
  14. sleep(1)
  15. @run_time
  16. def fun_three():
  17. sleep(1)

带参数的装饰器

通过前面简单的例子应该已经明白装饰器的价值和它的简单用法:通过闭包来实现装饰器,函数作为外层函数的传入参数,然后在内层函数中运行、附加功能,随后把内层函数作为结果返回。
除了上述简单的用法还有一些更高级的用法,比如用装饰器进行类型检查、添加带参数的的装饰器等。它们的用法大同小异,关于高级用法,这里以带参数的装饰器为例进行介绍。

  • 不要把问题想的太复杂,带参数的装饰器其实就是在上述基本的装饰器的基础上在外面套一层接收参数的函数,下面通过一个例子说明一下。

以上述例子为基础,前面的简单示例输出的信息是,

  1. func three run time 1.0003271102905273
  2. func three run time 1.0006263256072998
  3. func three run time 1.000312328338623

现在我认为这样的信息太单薄,需要它携带更多的信息,例如函数名称、日志等级等,这时候可以把函数名称和日志等级作为装饰器的参数,下面来实现一下。

  1. def logger(msg=None):
  2. def run_time(func):
  3. def wrapper(*args, **kwargs):
  4. start = time()
  5. func() # 函数在这里运行
  6. end = time()
  7. cost_time = end - start
  8. print("[{}] func three run time {}".format(msg, cost_time))
  9. return wrapper
  10. return run_time
  11. @logger(msg="One")
  12. def fun_one():
  13. sleep(1)
  14. @logger(msg="Two")
  15. def fun_two():
  16. sleep(1)
  17. @logger(msg="Three")
  18. def fun_three():
  19. sleep(1)
  20. fun_one()
  21. fun_two()
  22. fun_three()

可以看出,我在示例基本用法里编写的装饰器外层又嵌套了一层函数用来接收参数msg,这样的话在每个函数(func_one、func_two、func_three)前面调用时可以给装饰器传入参数,这样的输出结果是,

  1. [One] func three run time 1.0013229846954346
  2. [Two] func three run time 1.000720500946045
  3. [Three] func three run time 1.0001459121704102

自定义属性的装饰器

这块没太懂

保留函数的元信息

很多教程中都会介绍装饰器,但是大多数都是千篇一律的围绕基本用法在展开,少部分会讲一下带参数的装饰器,但是有一个细节很少有教程提及,那就是保留元信息的装饰器

  • 函数的元信息是:就是函数携带的一些基本信息,例如函数名、函数文档等,我们可以通过func.name获取函数名、可以通过func.doc获取函数的文档信息,用户也可以通过注解等方式为函数添加元信息。 ```python from time import time

def run_time(func): def wrapper(args, *kwargs): start = time() func() # 函数在这里运行 end = time() cost_time = end - start print(“func three run time {}”.format(cost_time)) return wrapper

@run_time def fun_one(): ‘’’ func one doc. ‘’’ sleep(1)

fun_one()

print(funone.name) print(funone.__doc)

输出

wrapper

None

  1. - 可以看出,通过使用装饰器,函数**fun_one**的元信息都丢失了,那怎么样才能保留装饰器的元信息呢?
  2. - 可以通过使用Python自带模块**functools**中的**wraps**来保留函数的元信息,
  3. ```python
  4. 作者:Jackpop
  5. 链接:https://www.zhihu.com/question/325817179/answer/798679602
  6. 来源:知乎
  7. 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  8. from time import time
  9. from functools import wraps
  10. def run_time(func):
  11. @wraps(func) # <- 这里加 wraps(func) 即可
  12. def wrapper(*args, **kwargs):
  13. start = time()
  14. func() # 函数在这里运行
  15. end = time()
  16. cost_time = end - start
  17. print("func three run time {}".format(cost_time))
  18. return wrapper
  19. @run_time
  20. def fun_one():
  21. '''
  22. func one doc.
  23. '''
  24. sleep(1)
  25. fun_one()
  26. print(fun_one.__name__)
  27. print(fun_one.__doc__)
  28. # 输出
  29. # fun_one
  30. # func one doc.