1. 什么是装饰器
装饰器就是用来为被装饰的对象新增功能的工具/函数,被装饰的对象可以是任意可调用的对象,装饰器本身也可以是任意可调用的对象
2. 为何要用装饰器
开放封闭原则:对修改封闭,对扩展开放
装饰器需要遵循的原则:
1. 不能修改被装饰对象的源代码
2. 不能修改被装饰对象的调用方式
装饰器模板
无参装饰器
def outter(func):
def wrapper(*args,**kwargs):
res=func(*args,**kwargs)
return res
return wrapper
有参装饰器
def auth(这里写需要传入的参数):
def outter(func): #func就是传入最原始的函数的内存地址,其实就是将被装饰的函数功能传入
def wrapper(*args,**kwargs):
res=func(*args,**kwargs)
return res
return wrapper
return outter
@auth(这里写需要传入的参数) #装饰语法的后面括号里加参数,即可传入
PS:如要添加功能可在装饰器的res=func(*args,**kwgars)的前后加入需要的功能
PS:有参装饰器最多三层,有参的在三层装饰器最顶部可以传入无限个参数
PS:装饰器的目标:就是要在遵循原则1和2的前提下,为被装饰对象新增功能
3. 如何实现装饰器
import time
def index(): #这是被装饰对象
time.sleep(1)
print('welcome to index page')
def timmer(func): # func=最原始那个index的内存地址,func可以理解为就是被装饰的函数,当做参数传入装饰器 #这个就是装饰器格式
def wrapper():
start=time.time()
func()
stop=time.time()
print('run time is %s' %(stop - start))
return wrapper
index=timmer(index) #index=wrapper(index的内存地址),这两个index不一样,前面的是一个新的名称空间,后面的index做了一个转换 #这一步就是用来包装,让使用者感觉没有变化但是后台却对功能进行了一个升级
index()
PS:为什么要在嵌套函数的外面再加一层函数,因为如果不加这层函数,则最后包装调用时候无法传入index整个参数
装饰器语法糖
格式:在被装饰对象正上方单独一行写@ + 装饰器的名字(例:@timmer)
其实就是装饰器(正下方的那个函数当做参数传入装饰器将返回的值赋值给原函数名)
用来取代这个操作index=timmer(index),其实就是一种简化语法
例:无参装饰器
import time
def timmer(func): #这个是装饰器,用来将index函数当做参数传入
def wrapper():
start=time.time()
func()
stop=time.time()
print('run time is %s' %(stop - start))
return wrapper
@timmer
def index():
time.sleep(1)
print('welcome to index page')
index()
PS:语法糖的步骤解析
1、写好装饰器先要将被装饰对象index函数当做参数传给装饰器timmer(即timmer(index))
2、然后将装饰器(index)再赋值给原函数名index(即index = timmer(index))
PS:所以@timmer = (index = timmer(index)),这一条是解析过程的解释,不是语法,请不要搞混
PS:被装饰器一定要写在装饰器的下面,不然语法糖无法正确执行
例:有参装饰器
import time
def timmer(func):
def wrapper(*args,**kwargs):
start=time.time()
func(*args,**kwargs)
stop=time.time()
print('run time is %s' %(stop - start))
return wrapper
@timmer
def home(name):
time.sleep(2)
print('welcome %s to home page' %name)
index()
PS:当被装饰对象是有参数的,那么必须在装饰器的局部函数加上*和**两个可变长参数,在内部调用的函数也加上可变长参数,这样在传入的时候不管是什么参数都可以正确传入
PS:有参装饰器既能修饰有参的函数,也能修饰无参的函数
例:有参且有返回值的装饰器
import time
def timmer(func):
def wrapper(*args,**kwargs):
start=time.time()
res = func(*args,**kwargs) #需要查看返回值必须将执行函数放入一个变量
stop=time.time()
print('run time is %s' %(stop - start))
print('>>>>',res)
return res #在末尾返回这个变量
return wrapper
@timmer #这个是没有返回值的被装饰函数
def index():
time.sleep(1)
print('welcome to index page')
@timmer #这个是有返回值的被装饰器
def home(name):
time.sleep(2)
print('welcome %s to home page' %name)
return 11111 #返回值是1111
index()
home('karl') #这里的实参是传递给最原始的函数,就是传递给home(name)这个形参的,让函数内部调用
PS:需要装饰器返回被装饰函数的值,在装饰器内直接将调用的函数放入变量即可,然后在末尾返回这个变量
多层有参装饰器
import time
current_user={'username':None}
def outter(engine='file'):
def auth(func):
def wrapper2(*args,**kwargs):
if current_user['username']:
res = func(*args, **kwargs)
return res
name=input('username>>: ').strip()
pwd=input('password>>: ').strip()
if engine == 'file':
print('基于文件的认证')
if name == 'egon' and pwd == '123':
current_user['username']=name
res=func(*args,**kwargs)
return res
else:
print('账号密码错误...')
elif engine == 'mysql':
print('基于mysql的认证')
elif engine == 'ldap':
print('基于ldap的认证')
else:
print("不合法的认证源")
return wrapper2
return auth
@outter(engine='file') #这个装饰器就是装饰下方的函数,括号里就是将参数传入装饰器,调用下面函数的时候就会将括号里的参数传入装饰器,触发功能
def index():
print('index function')
time.sleep(1)
@outter(engine='mysql')
def home(name):
print('home function',name)
time.sleep(1)
index()
home('egon') #这里的egon是传给home函数内的功能调用的
装饰器的伪装
就是将装饰器的信息伪装成和被装饰函数的信息一抹一样
在装饰器内的函数顶部加一个@wraps即可
import time
from functools import wraps
def timmer(func):
@wraps(func) #就是在这个位置加@wraps
def wrapper(*args,**kwargs):
start=time.time()
res=func(*args,**kwargs)
stop=time.time()
print('run time is %s' %(stop - start))
return res
return wrapper
def index():
"""
这是一个index函数
:return:
"""
time.sleep(1)
print('welcome to index page')
index()
装饰器叠加多个装饰器
装饰器的加载顺序:自下而上
装饰器的执行顺序:
加载顺序实例:运行结果就是3,2,1
import time
def deco1(func1): #func1=wrapper2的内存地址
print('deco1运行')
def wrapper1(*args,**kwargs):
res=func1(*args,**kwargs)
return res
return wrapper1
def deco2(func2): #func2=wrapper3的内存地址
print('deco2运行')
def wrapper2(*args,**kwargs):
res=func2(*args,**kwargs)
return res
return wrapper2
def deco3(func3): #func3=最原始那个index的内存地址
print('deco3运行')
def wrapper3(*args,**kwargs):
res=func3(*args,**kwargs)
return res
return wrapper3
# index=wrapper1
@deco1 # index=deco1(wrapper2)
@deco2 # wrapper2=deco2(wrapper3)
@deco3 # wrapper3=deco3(index) 最原始那个index的内存地址
def index():
print('index function')
time.sleep(1)
index()
PS:装饰器上方有多个装饰器,加载的时候的deco3先把index最原始的那个内存地址传入,然后把结果赋值给wrapper3,然后把wrapper3传入deco2,把结果返回给wrapper2,然后把wrapper2传入deco1,把结果返回给index,最后运行index得到一个结果
装饰器执行实例:运行结果就是1,2,3
import time
def deco1(func1): #func1=wrapper2的内存地址
def wrapper1(*args,**kwargs):
print('wrapper1')
res=func1(*args,**kwargs)
return res
return wrapper1
def deco2(func2): #func2=wrapper3的内存地址
def wrapper2(*args,**kwargs):
print('wrapper2')
res=func2(*args,**kwargs)
return res
return wrapper2
def deco3(func3): #func3=最原始那个index的内存地址
def wrapper3(*args,**kwargs):
print('wrapper3')
res=func3(*args,**kwargs)
return res
return wrapper3
# index=wrapper1
@deco1 # index=deco1(wrapper2)
@deco2 # wrapper2=deco2(wrapper3)
@deco3 # wrapper3=deco3(最原始那个index的内存地址)
def index():
print('index function')
time.sleep(1)
index() #wrapper1()
PS:运行的时候,deco1(wrapper1)调用了deco2(wrapper2),deco2(wrapper)调用了deco3(wrapper3),deco3(wrapper3)调用了index
装饰器的装饰也是有顺序的,顺序的不同也会影响功能触发
import time
current_user={'username':None}
def timmer(func): #这是一个运行时间的装饰器
def wrapper1(*args,**kwargs):
start=time.time()
res=func(*args,**kwargs)
stop=time.time()
print(stop - start)
return res
return wrapper1
def auth(func): #这是一个账户认证的装饰器
def wrapper2(*args,**kwargs):
if current_user['username']:
res = func(*args, **kwargs)
return res
name=input('username>>: ').strip()
pwd=input('password>>: ').strip()
if name == 'egon' and pwd == '123':
current_user['username']=name
res=func(*args,**kwargs)
return res
else:
print('账号密码错误...')
return wrapper2
@timmer #这个装饰器是装饰下面的auth装饰器的,而auth装饰器是装饰index的
@authdef
index():
print('index function')
time.sleep(1)index()
PS:如果@timmer写在@auth的上面,name这个装饰器就是用来统计用户验证的时间的,如果写在@auth的下面,则就是用来装饰index这个函数,统计index函数内功能运行的时间,所以装饰器的位置不同,也会对触发的功能有所影响
判断装饰器的执行顺序(小窍门):
装饰器调用的时候肯定是先加载,再执行
所以可以根据环形图知道加载的时候,先生成了这样的环形图,
1、index先传给了wrapper3(最原始的那个index)
2、将wrapper3传给了wrapper2
3、将wrapper2传给了wrapper1
PS:其实就是加载时候wrapper3(deco3)得到了最原始的index的内存地址后将wrapper2(deco2)包起来,wrapper2(deco2)将wrapper1(deco1)包起来,这时候wrapper1(deco1)就获取到了最原始的index的内存地址。
加载结束后,我们将最里层的赋值给了最index这个函数名(这个index和最原始的index这个函数无任何关系),然后我们执行的时候,就是先调用了最里层的那个wrapper1(deco1),wrapper1(deco1)调用了wrapper2(deco2),wrapper2(deco2)调用了wrapper3(deco3),wrapper3(deco3)最开始得到的就是最原始的index的内存地址,最终结果我们得到了最原始的index函数的功能,在调用过程中也实现的新功能的传递
PS:环形图的意思就是我们调用的时候wrapper1调用了wrapper2,wrapper2调用了wrapper3(最原始的那个index的内存地址),然后返回给wrapper2,wrapper2返回给wrapper1,wrapper1返回给了index,调用到了最原始的index内存地址,即没有改变原程序的代码,也没有改变调用方式,用户执行时候还是执行index就可以得到最原始的功能,也能得到新功能