在Python中,当需要对某个函数的功能进行增强,又不修改其代码,而是在运行期间动态为其添加功能的方式,便是装饰器。
本质上,装饰器其实就是一个返回函数的高阶函数,所以在学习装饰器之前要理解Python中一切皆为对象的思想。
def print_log(func):
def wrapper(*args, **kw):
print(f'call {func.__name__}')
return func(*args, **kw)
return wrapper
@print_log
def greet():
print('hello!')
以上就是一个最简单的用于打印日志的装饰器,把@pring_log
放到greet方法定义语句上,实际等于:
greet = print_log(greet)
在执行时,动态为greet函数增加了功能。可以看到,通过把greet函数作为对象传给装饰器print_log后,greet函数作为一个对象此时实际等于wrapper对象。当我们在调用greet()
时,其实执行的是print_log(greet)()
,也就是装饰器中retrun的新函数wrapper()
。(这里如果没有明白,要仔细理解函数也是对象这句话)
wrapper函数的参数定义是(*args, **kw),因此,wrapper()函数可以接受任意参数的调用
如果装饰器本身需要参数传入,那么我们要编写一个返回装饰器的高阶函数,会更复杂一些。假设我们要编写一个可以自定义日志输出语言的装饰器,那需要如下改动:
def print_content(content):
def decorator(func):
def wrapper(*args, **kw):
print(f"this is personal content : {content}")
print(f'call {func.__name__}')
return func(*args, **kw)
return wrapper
return decorator
@print_content("how are you?")
def greet():
print("hello!")
同样的,在方法定义加上@print_content
等于:
greet = print_content("how are you?")(greet)
先执行print_content("how are you?")
返回decorator对象,再将greet函数作为对象传给decorator,然后返回wrapper对象。只是多嵌套一层用于接收参数而已,本质上与之前的代码并没有不同。
经过上面一番操作后,函数的功能已经没有问题了。但是前文已经说过,函数也是对象,greet对象经过装饰器修改后,实际上已经是wrapper对象,我们在调用greet()
时,用的也是wrapper()
。此时的一些函数的内建属性,如__name__
属性已经由greet
变成了wrapper
。通过内置的functools.wraps
,即可轻松的解决这个问题:
import functools
def print_log(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print(f'call {func.__name__}')
return func(*args, **kw)
return wrapper
文章参考:
装饰器-廖雪峰:https://www.liaoxuefeng.com/wiki/1016959663602400/1017451662295584