装饰器本质上是一个 python 函数或类,它可以让其他函数或类在不需要做任何代码修改的前提下增加额外功能,或者说是抽离出与函数功能本身无关的雷同代码到装饰器中并继续重用,装饰器的返回值也是一个函数/类对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。@符号是装饰器的语法糖,在定义函数的时候使用,避免再一次赋值操作。
一、简单装饰器
下面是一个简单的装饰器,实现打印出函数执行时的时间的功能。func_demo函数被当作参数传入starttime装饰器函数中。
import time
def starttime(func):
def wrapper():
print("当前时间:%s" %(time.strftime("%Y-%m-%d %H:%M:%S")))
#func() #直接执行函数也可以
f = func()
return f
#return f+"!!!!!" #可以修改函数的返回值
return wrapper
@starttime
def func_demo():
print("Hello world")
func_demo()
输出:
当前时间:2020-09-15 23:00:23
Hello world
二、可以接收函数参数的装饰器
函数带参数时,装饰器要做一些修改,函数带什么参数,装饰器就要接收什么参数(参数名一致)。但是每个函数的参数是不一样的,所以用可变参数传递,函数调用时传入的参数,位置参数用*args
传递,关键字参数用**kwargs
传递。
import time
def logging(func):
def wrapper(*args, **kwargs):
print("%s 运行函数的名称:%s" %((time.strftime("%Y-%m-%d %H:%M:%S")), func.__name__))
f = func(*args, **kwargs)
print("函数参数:%s %s" %(locals().get("args"), locals().get("kwargs")))
print("%s 函数运行结果:%s" % ((time.strftime("%Y-%m-%d %H:%M:%S")), f))
return f
return wrapper
@logging
def func_demo(x,y="bbbbb"):
return str(x)+str(y)
result = func_demo("aaaaa", y="ccccc")
print(result)
输出:
2020-09-15 23:30:14 运行函数的名称:func_demo
函数参数:('aaaaa',) {'y': 'ccccc'}
2020-09-15 23:30:14 函数运行结果:aaaaaccccc
aaaaaccccc
三、装饰器带参数
装饰器也可以带参数,比如上面的例子中想让log指定级别输出。
eg1:
def use_logging(level="warn"):
def logging(func):
def wrapper(*args, **kwargs):
if level=="warn":
print("warn级别日志输出:")
print("运行函数的名称:%s" %func.__name__)
f = func(*args, **kwargs)
if level=="debug":
print("debug级别日志输出:")
print("函数参数:%s %s" %(locals().get("args"), locals().get("kwargs")))
print("函数运行结果:%s" %f)
return f
return wrapper
return logging
#装饰器不带参数时
@use_logging()
def func_1(x,y="bbbbb"):
return str(x)+str(y)
#装饰器带参数时
@use_logging(level="debug")
def func_2(x,y="bbbbb"):
return str(x)+str(y)
print(func_1("aaaaa", y="ccccc"))
print(func_2("aaaaa", y="ccccc"))
输出:
warn级别日志输出:
运行函数的名称:func_1
函数运行结果:aaaaaccccc
aaaaaccccc
debug级别日志输出:
函数参数:('aaaaa',) {'y': 'ccccc'}
函数运行结果:aaaaaccccc
aaaaaccccc
注:这种写法如果调用装饰器时不带参数,装饰器要加括号
eg2:
from functools import partial
def logging(func=None, level="warn"):
if func==None:
return partial(logging, level=level) #partial()函数返回自身
def wrapper(*args, **kwargs):
if level=="warn":
print("warn级别日志输出:")
print("运行函数的名称:%s" %func.__name__)
f = func(*args, **kwargs)
if level=="debug":
print("debug级别日志输出:")
print("函数参数:%s %s" %(locals().get("args"), locals().get("kwargs")))
print("函数运行结果:%s" %f)
return f
return wrapper
#装饰器不带参数时
@logging
def func_1(x,y="bbbbb"):
return str(x)+str(y)
#装饰器带参数时
@logging(level="debug")
def func_2(x,y="bbbbb"):
return str(x)+str(y)
print(func_1("aaaaa", y="ccccc"))
print(func_2("aaaaa", y="ccccc"))
输出:
warn级别日志输出:
运行函数的名称:func_1
函数运行结果:aaaaaccccc
aaaaaccccc
debug级别日志输出:
函数参数:('aaaaa',) {'y': 'ccccc'}
函数运行结果:aaaaaccccc
aaaaaccccc
注:在第二个例子中,装饰器不带参数时,可以不加括号。当装饰器不带参数时,func_1
会被当做第一个参数直接传递给logging
装饰器,调用方式为logging(func_1)
,这就要求所有其他参数都必须有默认值。当装饰器带参数时,调用方式为logging(level="debug")(func_2)
,初始调用时,func_2
并没有传递进来,这就要求装饰器内第一个参数必须是可选的(这也导致调用func_2
时必须以关键字参数形式传参),而且要返回一个接受一个函数参数并包装它的函数,以functools.partial
举例,参考partial()函数了解更多。
四、@wraps(func)
当装饰器作用在某个函数上时,这个函数的元信息比如名字、文档字符串、注解和参数签名都会丢失。
def decorator(func):
'''
装饰器
'''
def wrapper(*args:str, **kwargs):
"""底层包装函数"""
result = func(*args, **kwargs)
print("运行的函数:%s" %func.__name__)
return result
return wrapper
@decorator
def countdown(n:int):
"""
当n大于0时,n=n-1
"""
while n > 0:
n -= 1
print(countdown.__name__) #wrapper
print(countdown.__doc__) #底层包装函数
print(countdown.__annotations__) #{'args': <class 'str'>}
使用 functools
库中的 @wraps
装饰器注解底层包装函数可以解决这个问题。
from functools import wraps
def decorator(func):
'''
装饰器
'''
@wraps(func) #注解底层包装函数
def wrapper(*args:str, **kwargs):
"""底层包装函数"""
result = func(*args, **kwargs)
print("运行的函数:%s" %func.__name__)
return result
return wrapper
这时就可以显示原函数的信息了。
print(countdown.__name__)
print(countdown.__doc__)
print(countdown.__annotations__)
countdown
当n大于0时,n=n-1
{'n': <class 'int'>}
1. 函数装饰器可以直接在类中的方法上使用
以logging
装饰器为例
class Spam:
@logging
def output(self, x):
return x
s = Spam()
print(s.output(5))
输出:
warn级别日志输出:
运行函数的名称:output
函数运行结果:5
5
2. 装饰器执行顺序:从内到外
@a
@b
@c
def f():
#等效于
f = a(b(c(f)))