深入理解 Python 装饰器
前言
装饰器在 Python 工程应用非常的广泛, 比如日志,缓存等各个方面
函数装饰器
简单的装饰器
def simple_decorator(func):
def wrapper():
print("wrapper of decorator")
func()
return wrapper
def print_hello():
print('hello, simple decorator')
res = simple_decorator(print_hello)
res()
#输出
wrapper of decorator
hello, simple decorator
变量 res 指向了内部函数 wrapper(), 内部函数 wrapper()又会调用原函数 print_hello(),因此,调用 res(),会先打印'wrapper of decorator',然后打印出'hello, simple decorator' 这里的函数 simple_decorator()就是一个装饰器,真正需要执行的函数 print_hello()包裹在其中, 改变了其行为,但是原函数 print_hello()不变
为了让这个装饰器看起来更简洁,更优雅的表示,Python 提供@语法糖,那么上面代码可以改成
def simple_decorator(func):
def wrapper():
print("wrapper of decorator")
func()
return wrapper
@simple_decorator
def print_hello():
print('hello, simple decorator')
print_hello()
通过@语法糖就大大提高了函数的重复利用和程序的可读性
带参数的装饰器
通常,我们会把*args 和 **kwargs,作为装饰器内部函数 wrapper()的参数,*args 和**kwargs,表示接收任意数量和参数的类型
def simple_decorator(func):
def wrapper(*args, **kwargs):
print("wrapper of decorator")
func(*args, **kwargs)
return wrapper
@simple_decorator
def print_hello(name, age):
print('wrapper of decorator')
print(f'name = {name} age = {age}')
print_hello('lcd',18)
带有自定义参数的装饰器
有时候,装饰器执行的时候,需要传入参数, 内部函数的执行跟你装饰器的参数又很大的挂钩,比如我想要定义一个参数,来表示装饰器内部函数被执行的次数,那么就可以写成下面这种形式:
def repeat(num):
def my_decorator(func):
@functools.wrap
def wrapper(*args, **kwargs):
for i in range(num):
print('wrapper of decorator')
func(*args, **kwargs)
return wrapper
return my_decorator
@repeat(4)
def greet(message):
print(message)
greet('hello world')
# 输出:
wrapper of decorator
hello world
wrapper of decorator
hello world
wrapper of decorator
hello world
wrapper of decorator
hello world
装饰器@functools.wrap,它会帮助保留原函数的元信息(也就是将原函数的元信息,拷贝到对应的装饰器函数里)。
类装饰器
类装饰器的实现主要依赖函数call(),每当调用一个类的示例的时,函数call()就会被执行一次。
class Count:
def __init__(self, func):
self.func = func
self.num_calls = 0
def __call__(self, *args, **kwargs):
self.num_calls += 1
print('num of calls is: {}'.format(self.num_calls))
return self.func(*args, **kwargs)
@Count
def example():
print("hello world")
example()
example()
# 输出
num of calls is: 1
hello world
num of calls is: 2
hello world
我们定义了类 Count,初始化时传入原函数 func(),而call()函数表示让变量 num_calls 自增 1,然后打印,并且调用原函数。因此,在我们第一次调用函数 example() 时,num_calls 的值是 1,而在第二次调用时,它的值变成了 2。
装饰器嵌套
执行顺序从里到外,也就是从上到下
import functools
def my_decorator1(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print('execute decorator1')
func(*args, **kwargs)
return wrapper
def my_decorator2(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print('execute decorator2')
func(*args, **kwargs)
return wrapper
@my_decorator1
@my_decorator2
def greet(message):
print(message)
greet('hello world')
# 输出
execute decorator1
execute decorator2
hello world
装饰器的应用场景
- 身份认证
import functools
def authenticate(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
request = args[0]
if check_user_logged_in(request): # 如果用户处于登录状态
return func(*args, **kwargs) # 执行函数 post_comment()
else:
raise Exception('Authentication failed')
return wrapper
@authenticate
def post_comment(request, ...)
...
- 日志记录
线上测试某些函数的执行时间,那么,装饰器就是一种很常用的手段。
import time
import functools
def log_execution_time(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
res = func(*args, **kwargs)
end = time.perf_counter()
print('{} took {} ms'.format(func.__name__, (end - start) * 1000))
return res
return wrapper
@log_execution_time
def record_time():
time.sleep(3)
record_time()
# 输出
record_time took 3004.3250000000003 ms
- 参数校验
输入合理性校验
import functools
def validation_check(input):
@functools.wraps(func)
def wrapper(*args, **kwargs):
... # 检查输入是否合法
@validation_check
def neural_network_training(param1, param2, ...):
...
- 缓存
LRU cache,在 Python 中的表示形式是@lru_cache。@lru_cache 会缓存进程中的函数参数和结果,当缓存满了以后,会删除 least recenly used 的数据
总结
装饰器,其实就是通过装饰器函数,来修改原函数的一些功能,使得原函数不需要修改。