装饰器作为Python当中非常经典和实用的feature,在项目当中应用是非常广泛的,比如说记录运行时间,缓存,鉴权管理,等等都会使用到装饰器。我在学习装饰器的过程中,其实并不算非常的顺利,其中也遇到了不少的坑,不过当学完装饰器之后,使用起来,那别提有多爽了,在本文当中我将讲述一下我学习装饰器的历程,希望对大家有所启发,如有理解的不对或者不到位的地方,也欢迎各位读者斧正。
基础知识
函数作为Python当中的一等公民(first-class citizen),函数也是对象,可以吧函数复制与变量,代码如下:
def func(msg):
print(f"Got a message {msg}")
seed_message = func
seed_message("Hello World!")
这里,我们吧函数func
赋值给了变量send_message
,这样调用send_message
就相当于调用了func
。
函数同样可以作为参数传到另一个函数当中。
def get_message(msg):
return f'Got a messsage: {msg}'
def deal_msg(deal_strategy, msg):
print(deal_strategy(msg))
deal_msg(get_message, 'Hello World!')
在这个例子当中,我们将函数get_message
作为参数,传递到了deal_message
当中,然后调用它。
可以在函数中定义函数,也就是函数的嵌套,代码如下:
def func(msg):
def get_message(msg):
print(f"Got a message: {msg}")
return get_message(msg)
func("Hello World!")
这里,在函数func
内部定义了一个函数get_message
,然后调用后作为func
的返回值返回。
函数的返回值也可以是函数对象(闭包),比如下面的例子:
def closure():
def get_message(msg):
print(f"Got a message: {msg}")
return get_message
send_message = closure()
send_message("Hello World!")
这里,函数closure
的返回值是一个函数对象,之后将这个函数的返回值进行调用,得到了正确的输出。
简单的装饰器
有了上面的基础知识,我们可以来看今天的主角装饰器了,首先来看一个简单例子。
def decorator(func):
def wrapper():
print("wrapper of decorator")
func()
return wrapper
def retouched_func():
print("This is retouched function.")
retouched_func = decorator(retouched_func)
retouched_func()
这里,我们通过函数作为参数传递给decorator
函数,然后首先调用wrapper
打印第一句,然后调用func
也就是实际传递进来的参数retouched_func
打印第二句,这里,我们改变了retouched_func
的行为,但是retouched_func
函数本身并没有发生变化,也就是说,这个方法是无入侵的。
实际上,这段代码虽然实现了装饰器的功能,但是,这个看起来太不pythonic了,在Python当中有更加简单和优雅的表示:
def decorator(func):
def wrapper():
print("wrapper of decorator")
func()
return wrapper
@decorator
def retouched_func():
print("This is retouched function.")
retouched_func()
这里的@
实际上是一个语法糖,相当于之前的retouched_func = decorator(retouched_func)
写法,因为是语法糖,因此这个写法更加的简洁,因此,如果需要把一个函数做类似的装饰,只需要使用@
就可以了。
带参数的装饰器
这里,就会有一个问题,如果说有参数需要传递给装饰需要怎么办呢? 一个非常简单的做法就是可以在对应的装饰器函数wrapper
上添加对应的参数,例子如下:
def decorator(func):
def wrapper(msg):
print("wrapper of decorator")
func(msg)
return wrapper
@decorator
def retouched_func(msg):
print(f"This is retouched function. params is {msg}")
retouched_func("Hello World!")
那么,问题又来了,如果说,有另外一个函数也想用这个装饰器,但是这个新的函数有两个餐宿,又该怎么办呢?
通常情况下会使用*args
和**kwargs
,作为装饰器内部函数wrapper
的参数,其中*args
和**kwargs
,表示接受任意数量和类型的参数,因此装饰器就可以写成下面的形式:
def decorator(func):
def wrapper(*args, **kwargs):
print('wrapper of decorator')
func(*args, **kwargs)
return wrapper
带有自定义参数的装饰器
装饰器不仅可以接受原函数任意类型和数量的参数,除此之外,它还可以接受自定义的参数,举个例子,我们想写一个装饰器重复执行同一个函数,然后在装饰器可以自定义执行的次数。
def repeat(num):
def decorator(func):
def wrapper(*args, **kwargs):
for i in range(num):
print(f"wrapper of decorator, func run {i} times")
func(*args, **kwargs)
return wrapper
return decorator
@repeat(4)
def retouched_func():
print("Hello World!")
retouched_func()
修饰之后函数的变化
下面,我们来看一个有趣的现象,来看代码:
def decorator(func):
def wrapper(msg):
print("wrapper of decorator")
func(msg)
return wrapper
@decorator
def retouched_func(msg):
print(f"This is retouched function. params is {msg}")
print(retouched_func.__name__)
help(retouched_func)
这里,可以发现,retouched_func
被修饰之后,他的元信息变了,这里的函数已经被wrapper
取代了,为了解决这个问题,我们通常使用内置的一个装饰器@functools.wrap
,它会帮助保留原函数的元信息(也就是将原函数的元信息,拷贝到对应的装饰器函数里)。
import functools
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print("wrapper of decorator")
func(*args, **kwargs)
return wrapper
@decorator
def retouched_func(msg):
print(f"This is retouched function. params is {msg}")
print(retouched_func.__name__)
类装饰器
前文主要介绍了函数作为装饰器的做法,实际上,类也可以作为装饰器,类装饰器主要依赖于函数__call__()
,每当你调用一个类的示例时,函数__call__()
就会被执行一次,直接来看代码吧:
class Decorator:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print('This is in class decorator!')
return self.func(*args, **kwargs)
@Decorator
def example():
print("Hello World!")
example()
装饰器的嵌套
在Python当中,是支持多个装饰器的,调用顺序从上到下,来看一个例子就明白了:
import functools
def decorator1(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print('execute decorator1')
func(*args, **kwargs)
return wrapper
def decorator2(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print('execute decorator2')
func(*args, **kwargs)
return wrapper
@decorator1
@decorator2
def example(message):
print(message)
example('Hello World!')
装饰器的用法示例
到这里,装饰器的基本概念和语法,我就描述完了,下面来看几个实际使用装饰器的例子。
身份认证
身份认证很容易理解,比如登录APP的时候需要输入用户名和密码,然后服务端就会确认你的身份,如果认证通过就可以进行某些操作,否则便不可以,下面我们来看一个简单的代码示例。
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)
else:
raise Exception('Authentication failed')
return wrapper
@authenticate
def post_comment(request, *args, **kwargs)
pass
这里,装饰器会首先检查是否登录, 如果已经登录,便可以执行post_commet
的操作,否则便无法执行这个操作。
日志记录
日志记录是一个非常常见的案例,比如在实际工作中,我需要测试某个函数的执行时间,但是我又不想入侵这个函数添加计时的操作,那么装饰器就会极大的发挥它的无入侵性的这个特点:
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 calculate_similarity(items):
pass
这里,装饰器log_execution_time
记录某个函数的运行时间,并返回其执行结果。如果你想计算任何函数的执行时间,在这个函数上方加上@log_execution_time
即可。
缓存
缓存装饰器也非常的常见,Python就内置了一个LRU cache
的装饰器,具体的细节请自行查阅相关资料,这里我就不过多解释了。
总结
本文介绍了Python当中装饰器的用法,
Decorators is to modify the behavior of the function through a wrapper so we don’t have to actually modify the function.
然后介绍了几个装饰器典型的应用,合理使用装饰器,往往可以极大的提高程序的可读性和运行效率。