装饰器(重点)
一、装饰器介绍
1.1 为何要用装饰器
软件的设计应该遵循开放封闭原则,即对扩展是开放的,而对修改是封闭的。对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。对修改封闭,意味着对象一旦设计完成,就可以独立完成其工作,而不要对其进行修改。
软件包含的所有功能的源代码以及调用方式,都应该避免修改,否则一旦改错,则极有可能产生连锁反应,最终导致程序崩溃,而对于上线后的软件,新需求或者变化又层出不穷,我们必须为程序提供扩展的可能性,这就用到了装饰器。
1.2 什么是装饰器
‘装饰’代指为被装饰对象添加新的功能,‘器’代指器具/工具。装饰器的核心思想是在不改变被装饰对象"内部代码"和"原有调用方式"的基础之上添加额外的功能。
并且,装饰器不是一个新的知识点,它是我们之前学习的:名称空间,函数对象,闭包函数组合而来
二、装饰器的实现
时间模块
# 补充知识点:
import time # 时间模块
'''返回的结果是:从代码运行的那一刻到1970-1-1的秒数'''
print(time.time()) # 时间戳 # 1677557680.7924008
time.sleep(3) # 代码就会停止3秒
print('睡好了')
2.1 无参装饰器的推导流程
如果想为下述函数添加统计其执行时间的功能
import time
def index():
time.sleep(2)
print('from index')
index() # 函数执行
遵循不修改被装饰对象源代码的原则,我们想到的解决方法可能是这样
1.直接在调用index函数的前后添加代码
# 1. 在函数执行之前,要记录一下此时的时间戳
start_time = time.time()
# 2. 开始执行函数
index()
# 3. 函数执行完毕之后,再次记录此时的时间戳
end_time = time.time()
# 4. 统计函数的执行时间
print('执行了%s秒' % (end_time - start_time))
如果想要为另一个函数添加统计执行时间的功能,就需要重复上面四个步骤。如果要调用的地方比较多,就需要多次拷贝代码。一个简单的功能写了多行代码,不够简便。
相同的代码需要在不同的位置反复执行>>>可以想到函数
2.把统计执行时间做成函数形式
def get_time():
# 统计函数的执行时间
start_time = time.time()
index()
end_time = time.time()
print('执行了%s秒' % (end_time - start_time))
以上把函数体代码写死了,只能够执行index函数的执行时间,如何才能做到统计更多的函数运行的时间,可以直接传参变换统计的函数
3.传参
def get_time(func_name):
# 统计函数的执行时间
start_time = time.time()
func_name()
end_time = time.time()
print('执行了%s秒' % (end_time - start_time))
get_time(index)
# 想要调用home,把实参改成home即可
get_time(home)
虽然实现了一定的兼容性,但是并不符合装饰器的特征,第一种传参不写,>>>>>>只能考虑闭包
4.考虑用闭包函数传参
def outer(func_name):
# func_name = index # 写死了,想要写活放到形参位置
def get_time():
# 统计函数的执行时间
start_time = time.time()
func_name()
end_time = time.time()
print('函数的执行时间为>>>:', end_time - start_time)
return get_time
res = outer(index)
res()
res1 = outer(home)
res1()
以上还是改变了调用方式,想着如何变形呢?
5.变量名赋值绑定(******)
def outer(func_name):
# func_name = index # 写死了,想要写活放到形参位置
def get_time():
# 统计函数的执行时间
start_time = time.time()
func_name()
end_time = time.time()
print('函数的执行时间为>>>:', end_time - start_time)
return get_time
# res = outer(index) # 赋值符号的左边是一个变量名,可以随意的命名
# res1 = outer(index)
# jason = outer(index)
index = outer(index) # get_time的内存地址
index() # <=>get_time()
至此我们便实现了一个无参装饰器timer,可以在不修改被装饰对象index源代码和调用方式的前提下为其加上新功能。但我们忽略了若被装饰的函数是一个有参函数,便会抛出异常
2.2 装饰器解决参数问题
6.上述装饰器只能装饰无参函数,兼容性太差
def index(a):
time.sleep(0.1)
print('from index', a)
def func1(a, b):
time.sleep(0.2)
print('from func1, %s:%s' % (a, b))
def func2():
time.sleep(0.3)
print('from func2')
def outer(func_name):
# func_name = index
def get_time():
# 统计函数的执行时间
start_time = time.time()
func_name()
end_time = time.time()
print('执行了%s秒' % (end_time - start_time))
return get_time
# index = outer(index) # get_time的内存地址
# index(1)
# func1 = outer(func1)
# func1(1, 2)
# func2 = outer(func2)
# func2()
# 这样输出错误,此时index实际是get_time,而get_time是无参函数
# 应该在func_name函数要加上参数,它又在内部,该怎么加呢?
被装饰的函数不知道有没有参数以及有几个参数,如何兼容,使用可变长参数
7.*args, **kwargs
def index(a):
time.sleep(0.1)
print('from index', a)
def func1(a, b):
time.sleep(0.2)
print('from func1: %s,%s' % (a, b))
def func2(a, b, c):
time.sleep(0.3)
print('from func2', a, b, c)
def outer(func_name):
# func_name = index
def get_time(*args, **kwargs):
# 统计函数的执行时间
start_time = time.time()
func_name(*args, **kwargs)
end_time = time.time()
print('执行了%s秒' % (end_time - start_time))
return get_time
index = outer(index) # get_time的内存地址
index(1)
func1 = outer(func1)
func1(1, 2)
func2 = outer(func2)
func2(1, 4, 5)
2.3 解决返回值问题
8.如果被装饰的函数有返回值
import time
'''8.如果被装饰的函数有返回值'''
def index():
time.sleep(3)
print('from index')
return 'hello index'
def home(a, b):
time.sleep(2)
print('from home:%s,%s' % (a, b))
return 'home'
def outer(func_name):
def get_time(*args, **kwargs):
# 统计函数的执行时间
start_time = time.time()
res = func_name(*args, **kwargs) # 用变量来接收index(),也就是index的返回值
end_time = time.time()
print('执行了%s秒' % (end_time - start_time))
return res # 才是真正的函数的返回结果
return get_time
# index = outer(index)
# res = index()
# print(res) # None
home=outer(home)
home(1, 2)
print(home(1, 2)) # home
2.4 课堂练习题
写一个认证装饰器:
定义一个index函数,让用户输入用户名和密码,如果输入正确,就执行index函数,否则不能执行函数
2.4.1 认证装饰器
def index():
print('from index')
def home():
print('from home')
def login_auth(func_name):
# func_name = index
def auth():
# 1.让用户输入用户名和密码
username = input('username:>>>').strip()
password = input('password:>>>').strip()
# 2.认证,判断用户名和密码是否正确
if username == 'kevin' and password == '123':
func_name()
else:
print('认证失败,不能执行函数')
return auth
# index = login_auth(index) # auth的内存地址
# index() # auth()
home = login_auth(home)
home()
2.4.2 认证升华
如果有一个函数被认证成功,后续的函数都不在认证了
def index():
print('from index')
def home():
print('from home')
def transfer():
print('转账功能')
# 定义一个变量来存储是否验证成功
is_login = {'is_login': False}
# is_login = False
def login_auth(func_name):
# func_name = index
def auth():
# global is_login
# if is_login:
if is_login.get('is_login'):
res = func_name()
return res
# 1.让用户输入用户名和密码
username = input('username:>>>').strip()
password = input('password:>>>').strip()
# 2.认证,判断用户名和密码是否正确
if username == 'kevin' and password == '123':
res = func_name()
is_login['is_login'] = True
# res = func_name()
return res
else:
print('认证失败,不能执行函数')
return auth
index = login_auth(index) # auth的内存地址
index() # auth()
home = login_auth(home)
home()
transfer = login_auth(transfer)
transfer()
三、装饰器的固定模板
def outer(func):
def inner(*args, **kwargs):
print('在函数执行之前需要添加的功能')
res=func(*args, **kwargs)
print('在函数执行之后需要添加的功能')
return res
return inner
四、装饰器的语法糖
def outer(func):
def inner(*args, **kwargs):
print('在函数执行之前需要添加的功能')
res=func(*args, **kwargs)
print('在函数执行之后需要添加的功能')
return res
return inner
@outer # index=outer(index)
def index():
print('from index')
index()
@outer # home=outer(home)
def home():
print('from home')
home()
"""
专业语法:语法糖
装饰器的本质就是函数的执行
书写规范:
1. 语法糖要写在被装饰对象的头上
2. 原理:把紧贴着的函数名当作第一个参数自动传给@后面的函数调用
"""
五、双层装饰器
import time
def outer(func_name):
# func_name = index
def get_time(*args, **kwargs):
# 1. 在函数执行之前,要记录一下此时的时间
start_time = time.time()
# 2. 开始执行函数
func_name(*args, **kwargs)
# 3. 函数执行完毕之后,再次记录此时的时间
end_time = time.time()
# 4. 统计函数的执行时间
print('执行了%s秒' % (end_time - start_time))
return get_time
def login_auth(func_name):
# func_name = index
def auth():
# 1. 让用户输入用户名和密码
username = input('username:').strip()
password = input('password:').strip()
# 2. 要认证,判断用户名和密码是否正确
if username == 'kevin' and password == '123':
# 才是正常执行的函数
res=func_name()
return res
else:
print('认证失败,不能执行函数')
return auth
@login_auth # index=login_auth(get_time)
@outer # get_time=outer(index)
def index():
time.sleep(1)
print('from index')
index() # auth()
'''
双层语法糖 加载顺序由下往上
每次执行之后如果上面还有语法糖,则直接将返回值函数名传递给上面的语法糖,如果上面没有语法糖了,则变为 index = login_auth(get_time)
'''
六、装饰器修复技术
6.1 help
可以使用help(函数名)来查看函数的文档注释,本质就是查看函数的doc属性,但对于被装饰之后的函数,查看文档注释
print(help(index))
'''
输出结果是:
Help on function auth in module __main__:
auth()
# func_name = index
'''
6.2 functools模块
使用修复技术后被装饰对象就不容易查看了,但仍然不会改变index函数已经被装饰的结果,使用语句是:
from functools import wraps
@wraps(参数) # 此语句是写到外部函数中,参数是外部函数的参数(也就是被装饰对象的形参)
from functools import wraps
def login_auth(func_name):
# func_name = index
@wraps(func_name) # 修复技术是为了让被装饰对象不容易被察觉装饰了
def auth():
# 1. 让用户输入用户名和密码
username = input('username:').strip()
password = input('password:').strip()
# 2. 要认证,判断用户名和密码是否正确
if username == 'kevin' and password == '123':
# 才是正常执行的函数
res=func_name()
return res
else:
print('认证失败,不能执行函数')
return auth
print(help(index))
'''
输出结果是:
Help on function index in module __main__:
index()
# func_name = index
'''
七、三层装饰器
# 判断七句print执行顺序
def outter1(func1):
print('加载了outter1') # 3
def wrapper1(*args, **kwargs):
print('执行了wrapper1') # 4
res1 = func1(*args, **kwargs)
print('hello1') # 10
return res1
return wrapper1
def outter2(func2):
print('加载了outter2') # 2
def wrapper2(*args, **kwargs):
print('执行了wrapper2') # 5
res2 = func2(*args, **kwargs)
print('hello2') # 9
return res2
return wrapper2
def outter3(func3):
print('加载了outter3') # 1
def wrapper3(*args, **kwargs):
print('执行了wrapper3') # 6
res3 = func3(*args, **kwargs)
print('hello3') # 8
return res3
return wrapper3
@outter1 # index=wrapper1=outter1(wrapper2)
@outter2 # wrapper2=outter2(wrapper3)
@outter3 # wrapper3=outter3(index)
def index():
print('from index') # 7
index()
# 输出结果是:
# 加载了outter3
# 加载了outter2
# 加载了outter1
# 执行了wrapper1
# 执行了wrapper2
# 执行了wrapper3
# from index
# hello3
# hello2
# hello1
八、有参装饰器
当装饰器中需要额外的参数时>>>:有参装饰器
函数auth需要一个source_data参数,而函数login_auth与auth的参数都有其特定的功能,不能用来接受其他类别的参数,可以在login_auth的外部再包一层函数outter,用来专门接受额外的参数,这样便保证了在outter函数内无论多少层都可以引用到
def outter(source_data,a,b,c,d,e,f, *args, **kwargs):
# source_data='file'
def login_auth(func_name):
# func_name = index
def auth(*args, **kwargs):
# 1. 让用户输入用户名和密码
username = input('username:').strip()
password = input('password:').strip()
# 2. 要认证,判断用户名和密码是否正确
if source_data == 'file':
print('数据来源是文件')
elif source_data=='mysql':
print('数据来源是MySQL')
elif source_data=='oracle':
print('数据来源是oracle')
else:
print('认证失败,不能执行函数')
return auth
return login_auth
此时我们就实现了一个有参装饰器,使用方式如下
# 先调用outter('source_data'='file'),得到@login_auth,@login_auth的语法意义与无参装饰器一样
@outter('file') # @login_auth
def index():
print('from index')
# 如果有多个参数,也可以一起放在outter中
@outter('file',1,2,3,4,6,7,8) # @login_auth
def index():
print('from index')
'''当装饰器中需要额外的参数时>>>:有参装饰器'''
"""
函数名加括号执行优先级最高 有参装饰器的情况
先看函数名加括号的执行
然后再是语法糖的操作
"""