装饰器
【一】什么是装饰器
- 器:工具
- 装饰:为其他事物添加额外的功能
- 定义一个函数,这个函数的功能就是用来装饰其他函数的
- 也就是说这个函数就是用来给其他函数添加额外功能用的
提示:可调用对象有函数,方法或者类,此处我们单以本章主题函数为例,来介绍函数装饰器,并且被装饰的对象也是函数。
【二】开放封闭原则
- 开放:对拓展功能(增加功能)开放,意思是再源代码不做任何改变的情况下,为其增加功能
- 封闭:对修改源代码是封闭的
def inside(x,y):
print(x)
print(y)
- 装饰器:在不修改被装饰对象的源代码,也不修改调用方式的前提下,给装饰对象添加新的功能
【三】代码
【1】引入
- 用王者荣耀的开局语言模拟来引入
import time
def inside(group, s):
print('欢迎来到王者荣耀')
print(f'你出生在{group}方阵营')
print(f'敌军还有{s}秒到达战场')
time.sleep(s)
print('全军出击')
inside('红', 5)
"""
欢迎来到王者荣耀
你出生在红方阵营
敌军还有5秒到达战场
全军出击
"""
【2】需求
- 需要统计程序运行的时间
(1)方案一
import time
def inside(group, s):
start = time.time()
print('欢迎来到王者荣耀')
print(f'你出生在{group}方阵营')
print(f'敌军还有{s}秒到达战场')
time.sleep(s)
print('全军出击')
end = time.time()
print(end-start)
inside('红', 2)
"""
欢迎来到王者荣耀
你出生在红方阵营
敌军还有2秒到达战场
全军出击
2.000739574432373
"""
- 违反的封闭原则,没有修改函数的调用方式,但是修改了函数源代码,所以这个方案舍弃掉
(2)方案二
def inside(group, s):
print('欢迎来到王者荣耀')
print(f'你出生在{group}方阵营')
print(f'敌军还有{s}秒到达战场')
time.sleep(s)
print('全军出击')
start = time.time()
inside('红', 2)
end = time.time()
print(end - start)
"""
欢迎来到王者荣耀
你出生在红方阵营
敌军还有2秒到达战场
全军出击
2.000739574432373
"""
- 这种方法没有修改源代码也没有修改函数的调用方式
- 但是如果我们有100个函数都需要增加这个计算运行时间的功能
- 就会出现很多重复的代码,这是一种十分low的行为
- 所以这个方案也舍弃
(3)方案三
def inside(group, s):
print('欢迎来到王者荣耀')
print(f'你出生在{group}方阵营')
print(f'敌军还有{s}秒到达战场')
time.sleep(s)
print('全军出击')
def wrapper():
start = time.time()
inside('红', 2)
end = time.time()
print(end - start)
wrapper()
- 解决的方案二的代码冗余问题,也没有修改被装饰对象的源代码,同时还为其增加了新的功能
- 但是被装饰对象的调用方式被修改了
- 所以这个方案也得舍弃
(4)方案四
- 暂且先不考虑如何解决不修改调用方式的问题
- 看方案三的代码可以看到这个函数是一个死的函数
- 如果被装饰对象的形参发生了修改,那就没有办法 了
def inside(group, s, z):
print('欢迎来到王者荣耀')
print(f'你出生在{group}方阵营')
print(f'敌军还有{s}秒到达战场')
time.sleep(s)
print(f'{z}出击')
def wrapper(*args, **kwargs):
start = time.time()
inside(*args, **kwargs)
end = time.time()
print(end - start)
wrapper('红', 2, '炮车')
- 通过*args和**kwargs,我们只需要按照被装饰对象的形参格式,随意传参就行了。
- 可是这样一看,如果我们需要装饰其他的函数功能就无法达到效果了
- 我们不能把被装饰对象写死,所以引出了方案五
(5)方案五
def inside(group, s, z):
print('欢迎来到王者荣耀')
print(f'你出生在{group}方阵营')
print(f'敌军还有{s}秒到达战场')
time.sleep(s)
print(f'{z}出击')
def wrapper(*args, **kwargs):
start = time.time()
func(*args, **kwargs) ##这样子写会报红,应为wrapper没有接收到一个func
end = time.time()
print(end - start)
wrapper('红', 2, '炮车')
- 思考解决办法
- 给函数传参
- 直接通过参数传(wrapper的参数是原封不动的传给被装饰对象函数的,需要严格按照被装饰对象的形参,所以这个方法不可行)(False)
- 通过闭包函数传(True)
- 给函数传参
def inside(group, s, z):
print('欢迎来到王者荣耀')
print(f'你出生在{group}方阵营')
print(f'敌军还有{s}秒到达战场')
time.sleep(s)
print(f'{z}出击')
def outer(func):
def wrapper(*args, **kwargs):
start = time.time()
func(*args, **kwargs)
end = time.time()
print(end - start)
return wrapper # 注意不能加括号,加括号就变成调用了,我们只需要wrapper的内存地址
inside = outer(inside)
inside('红',2,'跑车')
- 这样子就没有修改被装饰对象的源代码,且没有修改调用方式
- 而且没有太多的代码冗余
- 就完成了给被装饰对象添加功能