装饰器的本质
在不改变被装饰对象原有的’调用方式’和’内部代码’
的情况下给被装饰对象添加新的功能
装饰器的原则
对扩展开放
对修改封闭
为何要用装饰器
软件的设计应该遵循开放封闭原则,即对扩展是开放的,而对修改是封闭的。
对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。
对修改封闭,意味着对象一旦设计完成,就可以独立完成其工作,而不要对其进行修改。
软件包含的所有功能的源代码以及调用方式,都应该避免修改,否则一旦改错,则极有可能产生连锁反应,最终导致程序崩溃,而对于上线后的软件,新需求或者变化又层出不穷,我们必须为程序提供扩展的可能性,这就用到了装饰器。
如何实现装饰器——无参装饰器
需求:统计 index 函数的运行时间,遵循 ‘开放封闭原则’
import time
def index():
print('from index')
time.sleep(2)
方案一:该方案改变了源代码
import time
def index():
start = time.time( )
print('from index')
time.sleep(2)
end = time.time( )
print(f'run time is {end-start}')
index()
方案二:遵循了’开放封闭原则’,但是装饰的功能要重复编写,代码冗余
import time
def index():
print('from index')
time.sleep(2)
start = time.time( )
index()
end = time.time( )
print(f'run time is {end-start}')
方案三:解决了代码冗余问题,但是功能写死了,只能装饰 index( ),同时对于被装饰的 index 函数的调用方式改变
import time
def index():
print('from index')
time.sleep(2)
def wrapper():
start = time.time( )
index()
end = time.time( )
print(f'run time is {end-start}')
wrapper()
wrapper()
wrapper()
方案四:装饰功能写活,但是对于原函数 index 的调用方式改变了
import time
def index():
print('from index')
time.sleep(2)
" wrapper 就是对 index 的装饰"
def wrapper(func):
start = time.time( )
func()
end = time.time( )
print(f'run time is {end-start}')
wrapper(index)
wrapper(index)
wrapper(index)
方案五:
import time
def index():
print('from index')
time.sleep(2)
def outter(func):
def wrapper():
start = time.time( )
func()
end = time.time( )
print(f'run time is {end-start}')
return wrapper
index = outter(index)
"""
调用了 outter ,返回了 wrapper 这个函数对象,再赋值给了 变量名 index
实际上 index 的调用就是 调用 wrapper
调用 wrapper 就是实现对 func 函数调用的同时,进行装饰,增加了统计运行时间的功能
而 func 是需要被传入的参数,真正传入的参数就是 index
所以最终实现了 在对 index 函数调用的同时,又添加了新的功能,同时遵循了开放封闭原则
"""
index()
综上,实现了对 index 的装饰,但是,如果换一个需要传入参数的函数,同样增加这个功能,就无法执行
def home(name):
time.sleep(5)
print('Welcome to the home page',name)
def outter(func):
def wrapper():
start = time.time( )
func()
end = time.time( )
print(f'run time is {end-start}')
return wrapper
home = outter(home)
home('jason')
"""
抛出异常:
TypeError: wrapper() takes 0 positional arguments but 1 was given
此时,由于 原始的 home 函数需要参数,而伪装的 home 函数 (其实就是 wrapper 函数) 没有位置形参来接受
"""
那么,就给伪装的 home 函数 (其实就是 wrapper 函数)增加一个位置形参来接收
def home(name):
time.sleep(5)
print('Welcome to the home page',name)
def outter(func):
def wrapper(name):
start = time.time( )
func(name)
end = time.time( )
print(f'run time is {end-start}')
return wrapper
home = outter(home)
home('jason')
修改过后,home 函数能够运行了,但是对于原来的 index 来说,又多传入了一个参数
import time
def index():
print('from index')
time.sleep(2)
def home(name):
time.sleep(5)
print('Welcome to the home page',name)
"借助 *args,**kwargs ,来解决传入的参数问题。"
def outter(func):
def wrapper(*args,**kwargs):
start = time.time( )
func(*args,**kwargs)
end = time.time( )
print(f'run time is {end-start}')
return wrapper
home = outter(home)
home('jason')
index = outter(index)
index()
对于 index 函数和 home 函数都是没有返回值的函数,如果被装饰者含有返回值,应当如何解决呢?
'调用伪装后的 sleep 函数 就是 调用 wrapper 函数,让伪装后的 sleep 函数有返回值,只要wrapper 有返回值即可,而 wrapper 的返回值必须是内部接受到的 func 调用后的返回值'
def sleep(time):
time.sleep(3)
return 3
def outter(func):
def wrapper(*args,**kwargs):
start = time.time( )
res = func(*args,**kwargs) # 修改处,用 res 接受到了 func 调用后的返回值
end = time.time( )
print(f'run time is {end-start}')
return res # 修改处, wrapper 函数返回值 res,就是 func 调用后的返回值
return wrapper
sleep = outter(sleep)
sleep()
装饰器的语法糖
对于被装饰的函数 (index, home, sleep ) 都需要一行代码,对 被装饰的原函数进行伪装
sleep = outter(sleep)
home = outter(home)
index = outter(index)
为了简化,引入了一种语法,首先将装饰器函数整体放在 被装饰函数的上方,然后在被装饰函数 定义的上方加上**@装饰器函数名**
def outter(func):
def wrapper(*args,**kwargs):
start = time.time( )
res = func(*args,**kwargs)
end = time.time( )
print(f'run time is {end-start}')
return res
return wrapper
@outter # 相当于 index = outter(index)
def index():
print('from index')
time.sleep(2)
@outter
def home(name):
time.sleep(5)
print('Welcome to the home page',name)
@outter
def sleep(time):
time.sleep(3)
return 3
叠加多个装饰器
装饰器模板: 相对于 原函数 func ,这个模板相当于什么装饰都没有,但是功能可以随时添加
def demo(func):
def wrapper(*args,**kwargs):
res = func(*args,**kwargs)
return res
return wrapper
登录认证功能装饰器,在模板上稍作修改
"登录前校验用户名和密码,校验成功才 调用登录函数 "
def auth(func):
def wrapper(*args,**kwargs):
inp_name = input('username: ').strip()
inp_pwd = input('password: ').strip()
if inp_name == 'franklin' and inp_pwd =='123':
res = func(*args,**kwargs)
return res
else:
print('login failed')
return wrapper
@auth # change_info = outter(change_info) = wrapper
def change_info(): # 修改用户信息的函数
print('from change_info')
叠加多个装饰器
def demo_1(func_1):
def wrapper_1(*args,**kwargs):
print('----from func_1----')
res_1 = func_1(*args,**kwargs)
return res_1
return wrapper_1
def demo_2(func_2):
def wrapper_2(*args,**kwargs):
print('----from func_2----')
res_2 = func_2(*args,**kwargs)
return res_2
return wrapper_2
def demo_3(func_3):
def wrapper_3(*args,**kwargs):
print('----from func_3----')
res_3 = func_3(*args,**kwargs)
return res_3
return wrapper_3
@demo_1
@demo_2
@demo_3
def index():
print('----index----')
return None
index()
print(index())
"""
----from func_1----
----from func_2----
----from func_3----
----index----
None
"""
对于叠加多个装饰器的被装饰函数的分析,应当从被装饰函数头上的第一个装饰器,从下向上分析
"在装饰器和被装饰函数的定义阶段"
@demo_1 '''最后:@demo_1 ==> wrapper_2 = demo_1(wrapper_2), func_1 传入的是 wrapper_2
demo1(wrapper_2) 返回的是 wrapper_1对象'''
@demo_2 '''其次:@demo_2 ==> wrapper_3 = demo_2(wrapper_3), func_2 传入的是 wrapper_3
demo2(wrapper_3) 返回的是 wrapper_2对象'''
@demo_3 '''首先:@demo_3 ==> index = demo_3(index), func_3 传入的是 原始未index
demo3(index) 返回的是 wrapper_3对象'''
def index():
print('----index----')
return None
所以,叠加多个装饰器之后, index 函数 是被 wrapper_1 函数给伪装了,调用 index 其实是调用 wrapper_1
"在最后的调用阶段"
index() '调用被 伪装后的 index 函数 ===> wrapper_1()'
def demo_1(func_1): # func_1 传入的是 wrapper_2
def wrapper_1(*args,**kwargs):
print('----from func_1----')
res_1 = func_1(*args,**kwargs)
return res_1
return wrapper_1
先打印 ----from func_1---- ,再调用 wrapper_2,将 wrapper_2 的返回值 赋给 res_1, wrapper_1 的返回值 res_1
'调用被 wrapper_2() '
def demo_2(func_2): # func_2 传入的是 wrapper_3
def wrapper_2(*args,**kwargs):
print('----from func_2----')
res_2 = func_2(*args,**kwargs)
return res_2
return wrapper_2
先打印 ----from func_2---- ,再调用 wrapper_3,将 wrapper_3 的返回值 赋给 res_2, wrapper_2 的返回值 res_2
'调用被 wrapper_3 () '
def demo_3(func_3): # func_3 传入的是 原始未装饰的index
def wrapper_3(*args,**kwargs):
print('----from func_3----')
res_3 = func_3(*args,**kwargs)
return res_3
return wrapper_3
先打印 ----from func_3---- ,再调用 原始index,将 原始index 的返回值 赋给 res_3, wrapper_3 的返回值 res_3
调用 wrapper_1
打印 ----from func_1----
调用 wrapper_2--> 打印 ----from func_2----
再调用 wrapper_3 -->打印 ----from func_3----
再调用 原始index-->|打印 ----index----
-->|原始 index 的返回值 None 赋给 res_3
-->|wrapper_3 的返回值 res_3 = None
wrapper_3 的返回值 None 赋给 res_2 <-----------
wrapper_2 的返回值 res_2 = None
wrapper_2 的返回值 None 赋给 res_1 <-----------
wrapper_1 的返回值 res_1 = None
wrapper_1 返回 None <-----------
就是被装饰后的 index 返回 None ,所以 print(index()) 打印出来的是就是 None
总结:按照装饰器从上到下的顺序,依次执行内部的 wrapper 函数,最后执行原始的函数,原始函数的返回值就是被装饰后函数的返回值
@demo_1 ---> 运行 demo_1 中的 wrapper_1, wrapper_1 中需要运行wrapper_2
@demo_2 ---> 再运行 demo_2 中的 wrapper_2,wrapper_2 中需要运行wrapper_3
@demo_3 ---> 然后运行 demo_3 中的 wrapper_3,wrapper_3 中需要运行原始 index
---> 最后运行 原始 index
"""
----from func_1----
----from func_2----
----from func_3----
----index----
"""
实例:
import time
# 统计运行时间
def timer(func):
def wrapper(*args,**kwargs):
start = time.time( )
res = func(*args,**kwargs)
end = time.time( )
print(f'run time is {end-start}')
return res
return wrapper
# 登录校验装饰器
def auth(func):
def wrapper(*args,**kwargs):
inp_name = input('username: ').strip()
inp_pwd = input('password: ').strip()
if inp_name == 'franklin' and inp_pwd =='123':
res = func(*args,**kwargs)
return res
else:
print('login failed')
return wrapper
# 需要被装饰的函数
def index():
print('----index----')
return None
当 两个装饰的 位置不同时,运行的结果也是不同的
@auth
@timer
def index():
...
index()
"先登录校验,再运行统计时间的函数,最后运行原始 index, 统计的是 原始 index 的运行时间"
@auth
@timer
def index():
...
index()
"先运行统计时间的函数,再登录校验,最后运行原始 index ,统计的是 登录校验 + 原始 index 的运行时间"
有参装饰器
如果有不同的认证方式,如何把认证功能写活?
'用一个 mode 参数来控制不同方式进行认证 , 问题在于 mode 这个参数应该这么传入 '
def auth(func):
def wrapper(*args,**kwargs):
inp_name = input('username: ').strip()
inp_pwd = input('password: ').strip()
# 基于文件
if mode == 'file':
if inp_name == 'franklin' and inp_pwd =='123':
res = func(*args,**kwargs)
return res
else:
print('login failed')
# 基于mysql
elif mode == 'MySQL':
print('基于 mysql 的认证')
# 基于其他方式
else:
print('基于 其他方式 的认证')
return wrapper
首先:def wrapper(mode, *args,**kwargs) 括号内不能添加
其次:def auth(func,mode ) 由于语法糖,也不能添加
所以,由于不能直接添加,考虑再用闭包函数,给 auth 再套一层函数
def outter(mode): # 最外层传入 mode 参数
def auth(func):
def wrapper(*args,**kwargs):
... # wrapper 内代码 需要用到 mode
return wrapper
return auth
auth = outter('mysql') # 调用 outter 返回的就是 函数对象 auth
@auth # 此时,相当于已经把 mode 传入
def index():
print('index')
简化: @outter(‘mysql’) -->outter(‘mysql’) 返回的就是 函数对象 auth,所以 = @auth