一、装饰器的基本使用
在不改变函数源代码的前提下,给函数添加新的功能,这时就需要用到“装饰器”。
0.开放封闭原则
写代码要遵循开放封闭原则,它规定“已经实现的功能代码不允许被修改,但可以被扩展。”——封闭即是已经实现的功能代码块,开放是对扩展开放。
1.装饰器本质
装饰器本质上就是一个闭包 ,但是是一个特殊的闭包。当闭包函数有且只有一个参数,并且该参数必须是函数类型,此时闭包才称为“装饰器”。
2.装饰器的功能特点
1)给已有函数增加额外的功能;
2)但是不修改已有函数的源代码;
3)不修改已有函数的调用方式;
3.装饰器定义格式
# 定义装饰器
def decorator(func):
def wrapper(*args, **kwargs):
要添加的额外功能
return func(*args, **kwargs)
return wrapper
其中:
1)func:是一个函数(函数作为参数);
2)wrapper:内层闭包函数,调用了func;
3)return wrapper:外层函数decorator返回值为内层函数wrapper;
4.装饰器的使用
先定义要装饰的函数:
def print_info():
print('打印的内容……')
为函数print_info添加功能:打印前提示,打印后提示
# 定义装饰器
def decorator(func):
def wrapper(*args, **kwargs):
print('***我要开始打印正文了***')
func()
print('***全文已打印完毕***')
return func(*args, **kwargs)
return wrapper
# 使用装饰器:将print_info函数作为装饰器的参数
print_info = decorator(print_info)
print_info()
>>> 运行结果
***我要开始打印正文了***
打印的内容……
***全文已打印完毕***
可以看出,1)为函数print_info添加功能成功;2)没有修改原函数;3)原函数调用方式也没变;
装饰器内部运行原理:
说明:
1)print_info = decorator(print_info),此处只装饰函数,并且把装饰器的内层函数返回,并赋值给print_info,此时print_info实际已经是wrapper函数;
2)print_info(),此处调用函数时,才正式开始执行已经添加功能的函数(wrapper函数);
3)wrapper函数中的func()调用的是原函数,因为闭包函数能够保存它使用的外层函数的变量;
5.注意事项
由上面可以看出装饰原函数后,原函数print_info的函数名已经变成wrapper(print_info.__name__='wrapper')。
为了不改变原函数的函数名,需要把原始函数的__name__等属性复制到wrapper()函数中,否则,有些依赖函数签名的代码执行就会出错。Python内置的装饰器functools.wraps能够实现。因此严格意义上装饰器的定义应为:
import functools
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
要添加的额外功能
return func(*args, **kwargs)
return wrapper
6.装饰器的装饰方式
方式一:直接调用
func = decorator(func) # 直接调用装饰器函数,传入函数名,并赋值给函数名
但每次都写调用-赋值语句比较麻烦,于是Python提供了比较简单的第二种“语法糖”书写格式
方式二:语法糖--@装饰器名字
@decorator # 在函数定义语句上面直接“@装饰器名字”
def func():
...
语法糖装饰是常用的装饰器装饰方式,“@decorator”就等价于func = decorator(func)。
装饰器在模块进行加载时就会立即执行装饰过程。
7.装饰器使用场景
1)引入日志;
2)函数执行时间统计;
3)执行函数前预备处理;
4)执行函数后清理功能;
5)权限校验等场景;
6)缓存;
8.应用举例
统计函数的执行时间:
# 统计函数执行时间的装饰器
import functools
import time
def execute_time(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
# 程序开始时间点
start = time.time()
# 执行函数
result = func(*args, **kwargs)
# 程序结束时间点
end = time.time()
# 计算执行时间
execution_time = end - start
print('{}执行时间:{}s'.format(func.__name__, execution_time))
# 返回函数结果
return result
return wrapper
# 定义一个简单的能够指定循环次数的函数,并装饰
@execute_time
def loop_count(n):
# 循环打印
for i in range(n):
print('第{}次...'.format(i+1))
# 看看循环1000000次需要多长时间?
loop_count(1000000)
>>> 运行结果:
第1次...
第2次...
...
第1000000次...
loop_count执行时间:6.355999231338501s
二、通用装饰器
要装饰的函数可能不需要传参,也可能需要传参;然后传多少个参数,是位置传参还是关键字传参,有没有返回值,这些都不能事先知道。也就是说,不可能针对性地去定义装饰器。于是,就需要定义通用的装饰器。不论被装饰的函数是否需要传参,传几个参数,怎么传参,有无返回值,通用装饰器都能使用。
通用装饰器的定义格式:
def decorator(func):
def wrapper(*args, **kwargs):
func执行前要添加的额外功能
result = func(*args, **args)
func执行后要添加的额外功能
return result
return wrapper
说明:
1)内层闭包函数使用*args、**kwargs接收参数,能够应对所有形式的不定长传参;
2)result为被装饰函数的返回值,在闭包函数中return返回;
解决了传参和返回值的问题,实现了通用性。
三、多个装饰器
多个装饰器同时装饰一个函数
# 装饰器1:将原函数输入内容用“()”括起
def add_little_sign(func):
def wrapper1(*args, **kwargs):
origin_info = func(*args, **kwargs)
return '(' + origin_info + ')'
return wrapper
# 装饰器2:将原函数输入内容用“{}”括起
def add_big_sign(func):
def wrapper2(*args, **kwargs):
origin_info = func(*args, **kwargs)
return '{' + origin_info + '}'
return wrapper
# 被装饰函数:打印info
@add_big_sign # 外层
@add_little_sign # 内层
def print_info(info):
return info
# 调用装饰后的函数
print(print_info('Hello World!'))
>>> 运行结果:
{(Hello World!)}
可以看到,先实现了add_little_sign的功能,然后实现add_big_sign的功能。
1)上面语法糖的装饰过程等价于:
print_info = add_little_sign(print_info)
print_info = add_big_sign(print_info)
装饰过程:
原print_info函数 --> 执行“print_info = add_little_sign(print_info)” ,使用add_little_sign装饰原函数print_ifo --> print_info = wrapper1 --> 执行“print_info = add_big_sign(print_info)” ,使用add_big_sign装饰函数print_ifo(此时函数print_info已是函数wrapper1) --> print_info = wrapper2
当装饰完毕后,原print_info已经变身成了wrapper2,并且此时wrapper2中储存着传入的wrapper1。那么再调用此时的print_info,执行过程为:调用装饰后的print_info --> 调用wrapper2 --> wrapper2内部调用wrapper1 --> 执行wrapper1 --> 装饰“()” --> 再装饰“{}” --> 完毕。
2)使用多个装饰器时功能特性
(1)一般使用语法糖方式装饰;
(2)会先用内层的装饰,再用外层的装饰;
多个装饰器同时使用时,相当于先将内层的装饰器包裹住原函数,再在外面用外层的装饰器包裹。
四、带参数的装饰器
有时装饰器也需要携带参数,以实现对要添加功能的处理。
错误的装饰器携带参数的格式:
def decorator(func, a):
def wrapper(*args, **kwargs):
要添加的额外功能
(对参数a进行操作)
return func(*args, **kwargs)
return wrapper
@decorator(2) # 假如将参数a设置为2
def func():
代码块...
>>> 运行结果:
TypeError: 'int' object is not callable
因为装饰器只能接收一个参数,并且该参数必须是可调用的函数对象。
正确的装饰器携带参数的格式:
def create_decorator(a):
def decorator(func):
def wrapper(*args, **kwargs):
要添加的额外功能
(对参数a进行操作)
return func(*args, **kwargs)
return wrapper
return decorator
@create_decorator(2) # 假如将参数a设置为2
def func():
代码块...
>>> 运行结果:
会正常运行,不会再抛出异常
上述代码块,定义了一个返回装饰器的闭包,通过闭包能够存储外层函数变量的特性保存住外层函数的参数,然后在装饰器中进行处理。
装饰过程等价于:
decorator = create_decorator(2) # 传入 参数2,被储存至返回的装饰器内
func = decorator(func) # 这步开始才是真正的装饰器装饰过程
...
五、类装饰器
装饰器还有一种特殊的定义方法,就是通过定义一个类来定义装饰器。
class Decorator(object):
def __init__(self, func):
# 初始化操作在此完成
self.__func = func
# 实现__call__方法,表示对象是一个可调用对象,可以像调用函数一样进行调用。
# 重写类的可调用魔法方法
def __call__(self, *args, **kwargs):
# 添加装饰功能
要添加的额外功能
self.__func()
@Decorator
def comment():
print("发表评论")
comment()
说明:
在定义类的初始化方法时,定义一个私有实例属性,此属性是用来接收要装饰的函数对象;然后重写类的__call__方法,使对象被调用时实现原函数功能的基础上增加新功能。(拥有__call__方法能够使对象被调用,“对象()”会自动执行对象的__call__方法。)
上述装饰器装饰过程等价于:
comment = Decorator(comment) # 传入comment进行实例化对象
comment() # 此步将执行类的实例方法__call__,从而实现了增加的功能
传入原函数comment,Decorator接收并实例化出一个对象 --> comment = 实例化的对象 --> comment() --> 执行实例对象的__call__方法 --> 完成装饰。