什么是装饰器?
装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象午无需修改源代码而添加额外的功能。
常见装饰器;内置装饰器、类装饰器、函数装饰器、带参数的函数装饰器
装饰器的作用
- 抽离雷同代码加以重用
- 在不修改原函数源代码的前提下,增加额外的功能。
装饰器使用场景
- 鉴权
- 计时
- 记录日志
- 路由
- 异常处理
- 错误重试
- 缓存
先看一个例子
def bunian():
print('I am bunian func...')
# 调用函数
bunian()
接下来我想查看执行该函数花了多少时间,怎么办?
import time
def bunian():
start_time = time.time()
print('I am bunian func...')
end_time = time.time()
print(f'func bunian takes time: {end_time - start_time}')
# 调用函数
bunian()
# 结果
I am bunian func...
func bunian takes time: 1.9073486328125e-05
到这里,功能已经实现了。但是如果是我们还想继续给另外的一些函数也实现同样的功能。那我们是不是给每个函数都添加这几行代码?可以这样做但不推荐,因为这样不但不高效,而且很麻烦。因为你每个函数都要去这样添加几行代码。如果有某一种方式可以一次性解决所有的问题,那自然最好不过了,因此“装饰器”就诞生了。
在上面的例子中,函数本身的功能只是打印一句话而已,但是经过改造后的函数不仅要能够打印这一句话,还要能够显示函数执行所花费的时间,这相当于我要给这个函数添加额外的功能。其实“装饰器”就是专门给函数添加额外的功能的。
不使用装饰器,且不改动bunian()函数添加计算函数执行时间功能
函数在Python中是一等公民,那么我们可以考虑重新定义一个函数cost_time(),将bunian的引用传递给他,然后在cost_time()函数中调用bunian并进行计时,这样,我们就达到了不改动bunian定义但是又添加了额外功能的目的了。
import time
def bunian():
print('I am bunian func...')
def cost_time(func):
start_time = time.time()
# 此处调用bunian()函数
bunian()
end_time = time.time()
print(f'func bunian takes time: {end_time - start_time}')
# 调用函数
cost_time(bunian)
# 结果
I am bunian func...
func bunian takes time: 2.3126602172851562e-05
到这里你会发现,功能实现了,我们没修改bunian()函数中的代码,但是我们这次的调用和上面的例子已经不一样了。这里调用的时候是将bunian这个函数作为一个参数传递给了cost_time函数。上面调用是通过bunian()这样调用,这次是通过cost_time(bunian)这样调用的。
那么问题来了,如果bunian()函数被好多地方调用了,你难道要去改这N多处的调用?或者说你这个函数时要给其他的开发调用使用的,难道你更新一下,让其他几百个开发跟着你改?别告诉我用ctrl+f 搜索然后替换,太不程序员了,结果是你不会被打死也会被辞退。
通过前面例子一步步来认识装饰器
上面说到要修改调用很麻烦,那有没有办法不修改调用来实现呢?肯定是可以不修改调用来实现的。那问题来了,不修改调用来实现也就意味着我们调用bunian()的话要实现cost_time(bunian)的效果咯?没错,就是这样。Python中一切皆对象,我们可以将cost_time计时器函数赋值给bunian,这样不就解决了吗。如下:
import time
def bunian():
print('I am bunian func...')
def cost_time(func):
start_time = time.time()
# 此处调用bunian()函数
bunian()
end_time = time.time()
print(f'func bunian takes time: {end_time - start_time}')
# 将cost_time赋值给bunian
bunian = cost_time
# 调用函数
bunian()
你以为这样就可以了?实际上他会报错:cost_time() missing 1 required positional argument: 'func'
为什么报错了?这是因为将cost_time赋值给bunian之后,此时bunian和cost_time表示的同一个东西,但是cost_time带有一个参数func,而在调用bunian()的时候并没有传入任何参数,所以并不会成功。
既然如此,那我们就要想办法将参数给统一了。怎么统一?那就要再添加一个参数。
由于参数不统一,如果cost_time()并不是直接添加额外的功能,而是返回一个与bunian参数列表一致的函数。而原来cost_time需要添加额外功能的代码再在cost_time里面定义一个函数,由它去完成不就可以了吗,将cost_time(bunian)的返回值赋值给bunian,然后调用bunian()的代码完全不用修改。也就是说我们依然是调用bunian(调用代码没变),但是同样却达到了添加额外功能的效果。
import time
#原来的函数bunian
def bunian():
print('I am bunian func...')
#定义一个计时器
def cost_time(func):
'''
cost_time函数负责返回一个wrapper,wrapper的参数要与原来的bunian保持相同
这样一来,执行 bunian=cost_time(bunian) bunian完全等价于wrapper
wrapper函数负责添加额外功能
'''
def wrapper():
start_time = time.time()
func()
end_time =time.time()
print(f'func bunian takes time: {end_time - start_time}')
return wrapper
# 这里与前面的 “bunian=cost_time”是有所区别的
bunian=cost_time(bunian)
# 和原来调用bunian()一样,但是达到了添加额外功能的效果
bunian()
# 结果
I am bunian func...
func bunian takes time: 1.5974044799804688e-05
你会发现,到这里为止,我们的问题都解决了。
1、调用的时候使用bunian()来进行调用,没改变调用方式。
2、上面提到的bunian和cost_time参数不统一的问题已经解决,就是在cost_time函数中在嵌套了一个wrapper函数,俗称闭包函数。
3、添加了额外的功能。
这其实就是装饰器了。但这是装饰器的原生版本。Python中引入了一个专业名次叫”语法糖”的东西来实现装饰器。这种看上去会更专业、代码也更加美观。解决方式就是@函数名
的形式,然后放在需要加这个装饰器功能的函数的头上,如下:
import time
#定义一个计时器
def cost_time(func):
'''
cost_time函数负责返回一个wrapper,wrapper的参数要与原来的bunian保持相同
这样一来,执行 bunian=cost_time(bunian) bunian完全等价于wrapper
wrapper函数负责添加额外功能
'''
def wrapper():
start_time = time.time()
func()
end_time =time.time()
print(f'func bunian takes time: {end_time - start_time}')
return wrapper
#原来的函数bunian
@cost_time
def bunian():
print('I am bunian func...')
bunian()
# 结果
I am bunian func...
func bunian takes time: 1.621246337890625e-05
这就是装饰器的最终模型。这里的@cost_time
其实和ayunw=cost_time(bunian)
是完全等价的。但是@
符号有两个大作用。一是减少了代码书写量,二是让我们代码看上去更美观,更专业,行内人一眼就看明白了你这个就是一个装饰器。其实这个叫做无参装饰器
。
带参数的装饰器记录日志
来看一个函数装饰器的例子
def use_logging(func):
def wrapper(*args, **kwargs):
logging.warn("%s is running... " % func.__name__)
return func(*args, **kwargs)
return wrapper
def bar():
print('I am bar func...')
bar = use_logging(bar)
bar()
# 结果:
WARNING:root:bar is running... I am bar func...
根据上面的例子,这里介绍一下带参数的装饰器
def use_logging(level):
def decorator(func):
def wrapper(*args, **kwargs):
if level == "warn":
logging.warn("%s is running... " % func.__name__)
return func(*args)
return wrapper
return decorator
@use_logging(level="warn")
def foo(name='foo'):
print("I am %s func..." % name)
foo()
# 结果:
WARNING:root:foo is running... I am foo func...
装饰器通用模板结构
上面装饰器功能我们实现了。下面贴出一个装饰器的”一般模板”,也就是一个通用类型的模板。因为装饰器很灵活,没有什么固定绝对的模板,因此语法也是很灵活的,可以根据你的需求来进行调整。
def decorator(function):
'''
第一层函数为装饰器名称
function:参数,即需要装饰的函数
return:返回值wrapper,为了保持与原函数参数一致
'''
def wrapper(*arg, **args):
'''
内层函数,这个函数实现“添加额外功能”的任务
*arg,**args:参数保持与需要装饰的函数参数一致,这里用*arg和**args代替
'''
#这里就是额外功能代码
function() #执行原函数
#这里就是额外功能代码
return wrapper