Python“三大器”之装饰器
一、装饰器
1、什么是装饰器?
装饰:装饰、修饰
器:工具
装饰器:装饰的工具
(*****)“开放封闭”:装饰器必须要遵循“开放封闭”原则:
开放:对函数功能的添加是开放的
封闭:对函数功能的修改是封闭的
2、装饰器的作用?
在不修改被装饰对象源代码与调用方式的前提下添加新的功能。
装饰器必须遵循的两个原则:
不修改被装饰对象源代码
不修改被装饰对象的调用方式
ps:
被装饰对象指的是 ---> 需要添加功能的(函数)
装饰器指的是 ---> 被装饰对象添加的新功能的(函数)
3、为什么要使用装饰器?
可以解决代码荣誉问题,提高代码的可扩展性
4、怎么使用装饰器?
编写装饰器:
通过闭包函数来实现装饰器
装饰器的应用:
1、统计时间
2、登录认证
例子:
需求:需要统计一下下载电影的时间
方案一:函数调用(适合少次使用)
import time
def download_movie():
print("开始下载电影...")
# 模拟电影下载时间为3秒
time.sleep(3)
print("电影下载成功!")
start_time = time.time() # 获取当前时间戳
download_movie()
end_time = time.time() # 获取当前时间戳
print(f"消耗时间:{end_time - start_time}")
问题1:如果有多个被装饰对象,需要写多次统计时间的代码,导致代码冗余,于是有了方案二。
方案二:使用装饰器(适合多次调用,调用时直接在闭包函数内传入所需要使用装饰器的函数名称)
import time
def download_movie():
print("开始下载电影...")
# 模拟电影下载时间为3秒
time.sleep(3)
print("电影下载成功!")
# 模拟多个被装饰对象
def func1():
pass
# 模拟多个被装饰对象
def func2():
pass
# 装饰器:初级版
def time_record(func):
def inner():
# 获取当前时间戳
start_time = time.time()
func()
# 获取当前时间戳
end_time = time.time()
print(f"消耗时间:{end_time - start_time}")
return inner
download_movie = time_record(download_movie)
download_movie()
问题2:如果被装饰对象有返回值,有参数而且有多个参数,于是有了方案三。
方案三:方案二的代码优化,使用*args, **kwargs接收所有参数(形参,关键字参数等)import time
def download_movie(*args, **kwargs):
print("开始下载电影...")
# 模拟电影下载时间
time.sleep(3)
print("电影下载成功!")
return "movie.mp4"
# 装饰器最终版
def time_record(func):
def inner(*args, **kwargs): # *args **kwargs接收所有参数(形参,关键字参数等)
# 获取当前时间戳
start_time = time.time()
# 将被装饰对象需要接收的人以参数,原封不动的传给func(被修饰对象)
res = func(*args, **kwargs) #此处的fanc为被修饰对象download_movie()
# 获取当前时间戳
end_time = time.time()
# 统计结束,打印统计时间
print(f"消耗时间:{end_time - start_time}")
return res
return inner
# 这里的download_movie是inner()的返回值
download_movie = time_record(download_movie)
# download_movie() ---> inner() ,可传入任意参数,若传入参数,download_movie()函数需要修改代码块,否则不打印或者不执行传入参数
download_movie()
方案三完美的解决了方案一和方案二遗留下的问题,此处的装饰器可以作为所有装饰器的模板
(*****)装饰器模板(记牢)
def wrapper(func): def inner(*args, **kwargs): # *args **kwargs接收所有参数(形参,关键字参数等) # 调用被装饰对象,得到被装饰对象的返回值res res = func(*args, **kwargs) return res return inner
装饰器简例:
def wrapper(func):
def inner(*args, **kwargs):
'''
在此处可以添加新添加的功能代码块
'''
# 调用被装饰对象,得到被装饰对象的返回值res
res = func(*args, **kwargs)
'''
在此处也可以添加新添加的功能代码块
'''
return res
return inner
def func1():
print("hello")
func1 = wrapper(func1)
func1()
代码执行顺序:
def为定义函数,不执行
先执行同级代码,再执行下级函数体代码
二、装饰器语法糖
装饰器的语法糖,是属于装饰器的(语法糖是装饰器内置的,可以引用所有的语法糖)
@装饰器名字 装饰器的语法糖
注意:在使用装饰器语法糖时,装饰器必须定义在被装饰对象之上
例子:统计函数执行时间
不使用装饰器语法糖:
import time
# func函数执行三秒
def func():
time.sleep(3)
# 装饰器:统计函数执行时间
def wrapper(func): # func被装饰对象
def inner(*args, **kwargs): # *args, **kwargs是被装饰对象的参数
# 调用前增加新功能
start_time = time.time()
res = func(*args, **kwargs)
# 调用前增加新功能
end_time = time.time()
print(f"程序执行时间为:{end_time - start_time}秒")
return res # 调用被装饰对象,接收返回值
return inner
# 不使用装饰器语法糖
func = wrapper(func)
func()
使用装饰器语法糖:
import time
# 装饰器:统计函数执行时间
def wrapper(func): # func被装饰对象
def inner(*args, **kwargs): # *args, **kwargs是被装饰对象的参数
# 调用前增加新功能
start_time = time.time()
res = func(*args, **kwargs)
# 调用前增加新功能
end_time = time.time()
print(f"程序执行时间为:{end_time - start_time}秒")
return res # 调用被装饰对象,接收返回值
return inner
# 使用装饰器语法糖:使用装饰器语法糖时,装饰器必须定义在被装饰对象之上
@wrapper # @wrapper 就等于---> func = wrapper(func)
# func函数执行三秒
def func():
time.sleep(3)
func() # 因为语法糖可直接调用
三、装饰器练习题
编写一个装饰器,为多个函数加上认证的功能(用户的账号密码来源于文件),要求登录成功一次,后续的函数都无需再输入用户名密码
可写完对照
# 定义一个字典来做判断 user_info = { "user" : None, # username与user_info["user"]来判断是否已登录用户 } # 登录功能: def login(): # 判断用户没有登录时,执行 # 登陆功能 print("请先登录") username = input("请输入你的用户名:").strip() password = input("请输入您的密码:").strip() # 打开密码表,对比账户密码是否正确 with open("dir/passwd.txt", "r", encoding="utf-8") as f: for line in f: # print(line) name, pwd = line.strip("\n").split(":") # 得到[tank, 123] if username == name and password == pwd: print("登陆成功!") user_info["user"] = username else: print("登录失败") # 登录认证装饰器: def login_auth(func): def inner(*args, **kwargs): # 登录认证功能 # 如果已经登录,将被装饰对象直接调用并返回 if user_info.get("user"): res = func(*args, **kwargs) return res # 如果没有登录,执行登录功能 else: login() # 调用login()函数进行登录 return inner # func1、2、3都需要先登录才能使用,若登陆一次,后续就不需要再次登录 # 登陆之后可以使用的功能1 @login_auth def func1(): print("from func1") pass # 登陆之后可以使用的功能2 @login_auth def func2(): print("from func2") pass # 登陆之后可以使用的功能3 @login_auth def func3(): print("from func3") pass # 执行环节 while True: func1() input("延迟操作") func2() func3()
四、叠加装饰器
1、什么是叠加装饰器
在同一个被装饰对象中,添加多个装饰器并执行
2、为什么要使用叠加装饰器
不使用叠加装饰器会导致代码冗余,结构不清晰,可扩展性差,所以最好每一个新的功能都应该写一个新的装饰器
3、怎么使用叠加装饰器
使用方式:
@装饰1
@装饰2
@装饰3
def 被装饰对象():
pass
例子1:
我的需求:为被装饰对象,添加统计与登录认证功能
import time user_info = { "user": None } # 登陆功能 def login(): username = input("请输入您的账户:").strip() password = input("请输入您的密码:").strip() with open("dir/passwd.txt", "r", encoding="utf-8") as f: # 我在同级目录下创建了一个dir目录,然后在目录下创建了一个passwd的文本文档,里面内容为tank:123,然后回车另起一行保存 info = f.readline().strip("\n").split(":") name, pwd = info if username == name and password == pwd: print("登陆成功...") user_info["user"] = username else: print("登录失败...") # 登录认证装饰器 def login_auth(func): def inner1(*args, **kwargs): # 登录认证功能 if user_info.get("user"): res = func(*args, **kwargs) return res else: login() return func(*args, **kwargs) # 无论inner1中出现任何判断,最后都要返回“调用后的被装饰对象”:func(*args, **kwargs) return inner1 ##### 注意:登陆这里无论inner1中出现任何判断,最后都要返回“调用后的被装饰对象”:func(*args, **kwargs) # 统计时间装饰器 def time_record(func): def inner2(*args, **kwargs): print("开始统计...") start_time = time.time() res = func(*args, **kwargs) end_time = time.time() print(f"消耗时间为{end_time - start_time}") return res return inner2 # 装饰顺序:先装饰time_record,再装饰login_auth @login_auth # inner1 = login_auth(inner2) @time_record # inner2 = time_record(download_movie) def download_movie(): print("电影开始下载...") time.sleep(3) print("电影下载完成!") return "gtwz.mp4" # 执行顺序:先执行login_auth,再执行time_record # 只统计下载电影的时间 login() # 先调用用户登录,模拟用户已登录 download_movie()
注意:装饰器在调用被装饰对象时才会执行添加的功能
装饰的顺序:由下到上
执行的顺序:由上到下
附上图解:
例子2:通过例子看到执行过程:
def wrapper1(func1): def inner1(*args, **kwargs): print("1-----start") # 被装饰对象在调用时,如果还有其他装饰器,会先执行其他装饰器中的inner # 下一步执行inner2 res = func1(*args, **kwargs) print("1-----end") return res return inner1 def wrapper2(func2): def inner2(*args, **kwargs): print("2-----start") # 被装饰对象在调用时,如果还有其他装饰器,会先执行其他装饰器中的inner # 下一步执行inner3 res = func2(*args, **kwargs) print("2-----end") return res return inner2 def wrapper3(func3): def inner3(*args, **kwargs): print("3-----start") # 被装饰对象在调用时,没有其他装饰器 # 下一步执行index res = func3(*args, **kwargs) print("3-----end") return res return inner3 @wrapper1 # inner1 = wrapper1(inner2) @wrapper2 # inner2 = wrapper2(inner3) @wrapper3 # inner3 = wrapper3(index) # 被装饰对象 def index(): print("hello") """ 装饰顺序: 从下到上 执行顺序: 从上到下 本例子的执行顺序: inner1() inner2() inner3() index() inner3的res inner2的res inner1的res """
五、无参装饰器和有参装饰器
1、无参装饰器
在被装饰对象装饰时,没有传参数的装饰器
@wrapper1
@wrapper2
@wrapper3
无参装饰器模板:
# 无参装饰器模板: def wrapper(func): def inner(*args, **kwargs): # 添加代码块功能 res = func(*args, **kwargs) # 添加代码块功能 return res return inner
2、有参装饰器
有参装饰器:在某些时候,需要给用户的权限进行分类
@wrapper1(参数1)
@wrapper2(参数2)
@wrapper3(参数3)
有参装饰器模板:
# 有参装饰器模板: def outer(para): # 传入的para参数是用在inner()内部函数使用 def wrapper(func): def inner(*args, **kwargs): if para == "": # 功能1 # 添加调用前的功能代码块1 res = func(*args, **kwargs) # 添加调用后的功能代码块1 return res elif para == "": # 功能2 # 添加调用前的功能代码块2 res = func(*args, **kwargs) # 添加调用后的功能代码块2 return res else: # 功能3 # 添加调用前的功能代码块3 res = func(*args, **kwargs) # 添加调用后的功能代码块3 return res # 无论inner中出现任何判断,最后都要返回“调用后的被装饰对象”:func(*args, **kwargs) return inner return wrapper
例1:可通过传参查看用户权限/功能
def user_auth(user_role): def wrapper(func): def inner(*args, **kwargs): if user_role == "SVIP": # 添加svip的权限/功能 print(user_role) res = func(*args, **kwargs) return res elif user_role == "VIP": # 添加vip的权限/功能 print(user_role) res = func(*args, **kwargs) return res return inner return wrapper @user_auth("VIP") def index(): pass index()
六、wraps修复(了解)
wraps是一个修复工具,修复的是被装饰对象的空间
functools import wraps def wrapper(func): @wraps(func) # 修改名称空间:inner ---> func def inner(*args, **kwargs): """ 这里是装饰器 :param args: :param kwargs: :return: """ res = func(*args, **kwargs) return res return inner def index(): """ 这里是被装饰对象 :return: """ pass print(index) print(index.__doc__)