装饰器的入门到精通
关于装饰器,在面试时,经常会被问到这两个问题:
1、你都用过装饰器实现过什么样的功能? 2、请手写一个可以传参的装饰器?
这篇博客就根据这两个问题,带大家系统的学习装饰器的所有内容.希望对大家有所帮助.
- hello,装饰器
- 入门: 日志打印器
- 入门: 时间计时器
- 进阶: 带参数的函数装饰器
- 高阶: 不带参数的类装饰器
- 高阶: 带参数的类装饰器
- 使用偏函数与类实现装饰器
- 装饰类的装饰器
- wraps装饰器的作用
- 内置装饰器: property
- 其他装饰器: 装饰器实战
1. hello,装饰器
装饰器的使用方法很简单:
1. 先定义一个装饰器
2. 再定义你的业务函数或者类
3. 最后把装饰器加在这个函数上面
举个小栗子:
def decorator(func): def wrapper(*args, **kw): return func() return wrapper @decorator def function(): print("hello, decorator")
实际上,装饰器并不是编码必须性,意思就是说,你不使用装饰器完全可以,它的出现,应该是使我们的代码
-
更加优雅,代码结构更加清晰
-
将实现特定的功能代码封装成装饰器,提高代码复用率,增强代码可读性
2. 入门: 日志打印器
实现的功能:
1. 在函数执行前,先打印一行日志,通知要执行函数了
2. 函数执行完后,再打印一行日志,宣布函数执行完毕.
def log(func): #装饰函数,参数 func 是被装饰的函数 def inner(*args,**kwargs): print('start running: {} function'.format(func.__name__)) res = func(*args,**kwargs) # 执行主函数 print('stop running') return res return inner @log def main(): print('我是主函数')
输出结果:
start running: main function
我是主函数
stop running
3. 入门: 时间计时器
时间计时器,顾名思义,就是实现一个能够计算函数执行时长的功能.
import time def timer(func): def inner(*args,**kwargs): start = time.time() func(*args,**kwargs) #函数真正执行的地方 print('执行了 {}s'.format(time.time()-start)) #计算时长 return inner @timer def main(sleep_time): time.sleep(sleep_time)
main(10)
输出结果:
执行了 10.0073800086975098s
4. 进阶: 带参数的函数装饰器
通过上面两个简单的入门实例,大家应该能体会到装饰器的工作原理了.
不过,装饰器的用法还远不止如此. 回过头去看看上面的例子,装饰器是不能接收参数的。其用法,只能适用于一些简单的场景。不传参的装饰器,只能对被装饰函数,执行固定逻辑。
装饰器本身是一个函数,做为一个函数,如果不能传参,那这个函数的功能就会很受限,只能执行固定的逻辑。这意味着,如果装饰器的逻辑代码的执行需要根据不同场景进行调整,若不能传参的话,我们就要写两个装饰器,这显然是不合理的。
比如说,我们要循环执行某一个函数,循环的次数是随机指定的.
def loop(count): def wrap(func): def inner(*args,**kwargs): for i in range(count): func(i) return inner return wrap @loop(6) def main(i): print('第 {} 次循环'.format(i+1)) main()
输出结果:
第 1 次循环 第 2 次循环 第 3 次循环 第 4 次循环 第 5 次循环 第 6 次循环
5. 高阶: 不带参数的类装饰器
以上都是基于函数实现的装饰器,在阅读别人代码时,还可以时常发现还有基于类实现的装饰器。
基于类装饰器的实现,必须实现 __call__
和 __init__
两个内置函数。__init__
:接收被装饰函数__call__
:实现装饰逻辑。
还是以日志打印这个例子为例.
class Log: def __init__(self,func): #接收被装饰函数 self.func = func def __call__(self, *args, **kwargs): #实现装饰逻辑 print('[INFO]: {} 正在执行'.format(self.func.__name__)) res = self.func(*args,**kwargs) print('函数执行完毕') return res @Log def main(): print('我是主函数') main()
执行结果:
[INFO]: main 正在执行
我是主函数
函数执行完毕
6. 高阶: 带参数的类装饰器
上面不带参数的例子,只能打印 INFO 级别的日志,正常情况下,我们还需要打印 DEBUG, WARNING 等级别的日志.这就需要给类装饰器传入参数,指定日志级别了.
带参数和不带参数的类装饰器有很大不同:
__init__
:不再接收被装饰函数,而是接收传入参数。__call__
:接收被装饰函数,实现装饰逻辑。
class Logger(): def __init__(self,level='INFO'): #接收参数 self.level = level def __call__(self, func): # 接收被装饰函数,实现装饰逻辑 def wrap(*args,**kwargs): print('[{}]正在执行 {}'.format(self.level,func.__name__)) return func(*args,**kwargs) return wrap @Logger(level='WARNING') def main(): print('我是主函数') main()
执行结果:
[WARNING]正在执行 main
我是主函数