Python装饰器
本文为景霄-Python核心技术与实战的学习笔记,如要查看完整内容请点击链接。
从函数到装饰器
回顾函数
在Python中,函数也是对象,因而我们可以将函数赋予变量:
1 | def func(message): |
也可以将函数作为参数,传入另一个函数:
1 | def get_message(message): |
也可以进行函数嵌套:
1 | def func(message): |
如果在上述嵌套中,返回的对象是函数,那么就构成了闭包:
1 | def func_closure(): |
装饰器
一个较为简单的自定义装饰器实现:
1 | def my_decorator(func): |
变量greet
指向内部函数wrapper()
,内部函数包裹原函数greet()
。这里的my_decorator()
就是一个装饰器,它将真正需要执行的函数greet()
包裹在内,同时改变其行为,但不改变原函数。
所谓装饰器就是通过装饰器函数来修改(添加)原函数的一些功能,而不需要直接对原始函数进行修改。
Decorators is to modify the behavior of the function through a wrapper so we don’t have to actually modify the function.
在上述代码中,我们自己手动将greet()
函数传入my_decorator()
函数中,在Python中有着更为简洁的实现:
1 | def my_decorator(func): |
@
被称为语法糖,@my_decorator
相当于greet = my_decorator(greet)
语句。
带有参数的装饰器
如果原函数存在参数,那么如何定义装饰器?
直接在对应的装饰器函数wrapper()
上添加相应的参数:
1 | def my_decorator(func): |
但是如果想把这个装饰器用于另一个具有多个参数的函数时,上述做法就不适用。
此时就可以使用*args
和**kwargs
,如下所示:
1 | def my_decorator(func): |
带有自定义参数的装饰器
装饰器除了可以接受原函数任意类型和数量的参数外,还可以定义自己接收的参数。
例如,使用一个参数表示装饰器内部函数被执行的次数:
1 | def repeat(num): |
相当于在之前的my_decorator()
之外再添加了一层函数,并且在定义装饰器时首先生成了外层函数的对象,返回my_decorator()
。
原函数发生变化了吗?
对于上述例子,打印原函数的元信息:
1 | greet.__name__ |
发现,经过装饰器装饰后,原函数的信息发生了变化,变成了wrapper()
函数。
此时,可以使用内置装饰器@functools.wrap
来保留原函数的元信息:
1 |
|
类装饰器
除了函数可以被用作装饰器外,类也可以被用作装饰器。类装饰器主要通过函数__call__()
实现:
1 |
|
装饰器的嵌套
Python支持多个装饰器的嵌套:
1 |
|
上述代码从里向外执行:
1 | decorator1(decorator2(decorator3(func))) |
多层嵌套例子
1 |
|
装饰器的用法
我们可以将在执行一个函数之前需要做的事情交给装饰器去做,同时不改变原始函数的行为。
身份认证
在进行某个操作前,常需要首先进行身份认证,此时就可以使用装饰器完成,使用装饰器来完成身份验证的任务:
1 |
|
这样,每次在调用post_comment()
之前都会调用认证步骤。
日志记录
想要统计某段代码的运行耗时,就可以使用装饰器实现:
1 |
|
输入合理性检查
在大型公司的机器学习框架中,在调用机器进行模型训练前,常使用装饰器对器输入进行合理性检查,以避免输入错误带来的额外开销:
1 |
|
缓存
LRU cache在Python中表示为@lru_cache
,该装饰器会缓存进程中的函数参数和结果,当缓存满后,删除最近最久未使用的数据。
在实际工程中,可以将一些反复调用的函数使用@lru_cache
装饰器进行装饰,这样,当调用相同相同的函数对象时便会直接从缓存中获得结果,而不用再次调用函数。
1 |
|