装饰器
器:指的是工具,可以定义成函数
装饰:指的是为其他事物添加额外的东西点缀
合在一起的解释:
装饰器指的定义一个函数,该函数是用来为其他函数添加额外的功能
为何要用装饰器:
开放封闭原则:
- 开放:指的是对拓展功能是开放的
- 封闭:指的是对修改源代码是封闭的
装饰器就是在不修改被装饰器对象源代码以及调用方式的前提下,为被装饰对象添加新功能
如何使用装饰器:
需求:在不修改index函数的源代码以及调用方式的前提下为其添加统计运行时间的功能
def index(x,y):
time.sleep(3)
print('index %s %s' %(x,y))
解决方案一
import time
def index(x, y):
time.sleep(3)
print('index %s %s' % (x, y))
statr = time.time()
index(111, 222)
stop = time.time()
print(stop-statr)
'''
index 111 222
3.0009870529174805
'''
这个解决方法不但没修改被装饰器对象源代码以及调用方式,也实现了新功能。但是如果多次调用函数,就无法多次使用,如果再每次调用函数的时候,都在调用函数的位置加上上述方法,就形成了同样的代码,多次出现。
解决这个方法的思路可以是将此过程写成一个函数
解决方案二:
import time
def index(x, y):
time.sleep(3)
print('index %s %s' % (x, y))
def wapper():
statr = time.time()
index(111, 222)
stop = time.time()
print(stop-statr)
wapper()
'''
index 111 222
3.0009870529174805
'''
这个方案解决了相同代码出现多次的问题,但是函数的调用方式发生了改变。
解决方案二的优化一:
import time
def index(x, y):
time.sleep(3)
print('index %s %s' % (x, y))
def wapper(*args,**kwargs): #将index的参数写活了
statr = time.time()
index(*args,**kwargs)
stop = time.time()
print(stop-statr)
wapper(111,222)
这个优化方案可以以index函数的方式接收参数,但是调用的函数名字不是index,而且如果不仅仅想修饰index函数,也想修饰其他函数,如何再此优化
解决方案二的优化二:
import time
def home(name):
time.sleep(2)
print('welcome %s come home' %name)
def index(x, y):
time.sleep(3)
print('index %s %s' % (x, y))
def outter(func):
#func=index的内存地址
def wapper(*args,**kwargs):
statr = time.time()
func(*args,**kwargs) #调用index函数
stop = time.time()
print(stop-statr)
return wapper
index=outter(index) #首先将index函数的内存地址给了outter,wapper就会调用index函数,最后将wapper的内存地址返回,用index变量接收wapper内存地址
index() #此时的index调用的其实是wapper函数,而不是直接调用index函数
home=outter(home)
home('zhangs') #此时可以将装饰器给多个函数
优化二使用了闭包的概念,可以通过传入不同函数的内存地址,使得装饰器可以运用到多个函数。但其实这个方法还是有问题的,就是如果被装饰的函数需要返回值,这个优化无法接收返回值。
解决方案二的优化三:
import time
def home(name):
time.sleep(2)
print('welcome %s come home' % name)
def index(x, y):
time.sleep(3)
print('index %s %s' % (x, y))
return 123 #被修饰的函数拥有返回值
def outter(func):
def wapper(*args, **kwargs):
statr = time.time()
res = func(*args, **kwargs) #用变量res接收函数index的返回值
stop = time.time()
print(stop-statr)
return res #返回index函数的返回值
return wapper
index = outter(index)
ret = index(1, 2) #接收index函数的返回值
print(ret)
'''
index 1 2
3.0008342266082764
123
'''
语法糖
在进行装饰器操作的时候,每一次都需要执行两次重复的代码,就是定义一个与被修饰函数一样的变量,再将闭包函数的返回值赋值给变量,变量才可以进行函数调用
有没有一种方法可以省去这个复杂的过程?
import time
def outter(func):
def wapper(*args, **kwargs):
statr = time.time()
res = func(*args, **kwargs) #用变量res接收函数index的返回值
stop = time.time()
print(stop-statr)
return res #返回index函数的返回值
return wapper
@outter
def home(name):
time.sleep(2)
print('welcome %s come home' % name)
@outter
def index(x, y):
time.sleep(3)
print('index %s %s' % (x, y))
return 123 #被修饰的函数拥有返回值
index(1,2)
'''
index 1 2
3.0001492500305176
'''
需要将装饰器放在最上方,避免调用函数的时候,函数为定义。以及@符号一定要在被修饰的函数正上方,@紧跟装饰器名字
装饰器补充:
装饰器虽然表面可以将函数调用达到“偷梁换柱”的效果,但是当在查看被装饰器装饰前函数的内存地址,以及被装饰器装饰器后的内存地址
import time
def outter(func):
def wapper(*args, **kwargs):
statr = time.time()
res = func(*args, **kwargs) # 用变量res接收函数index的返回值
stop = time.time()
print(stop-statr)
return res # 返回index函数的返回值
return wapper
# @outter
def index(x, y):
time.sleep(3)
print('index %s %s' % (x, y))
return 123 # 被修饰的函数拥有返回值
# index(1, 2)
print(index.__name__)
'''
index
wapper
'''
由上述了解到使用了装饰器虽然可以达到直接调用函数的作用,但是在一些方面还是无法做到真正的“偷梁换柱”,还有被装饰的函数中的注释是否相等,等等
将原函数的属性赋值给wapper
import time
from functools import wraps
def outter(func):
#@wraps(func)
def wapper(*args, **kwargs):
statr = time.time()
res = func(*args, **kwargs)
stop = time.time()
print(stop-statr)
return res
return wapper
#@outter
def index(x, y):
time.sleep(3)
print('index %s %s' % (x, y))
return 123
index(1, 2)
print(index.__name__)
'''
index
index
'''
有参装饰器
有这么一个登录验证的代码:
def auth(func):
def wapper(*args, **kwargs):
name = input('请输入你的用户名:').strip()
pwd = input('请输入你的密码:').strip()
#从文件中取帐号密码
if name == 'aaa' and pwd == '123':
print('登录成功')
res = func(*args,**kwargs)
return res
else:
print('登录失败')
return wapper
@auth
def index(x,y):
print('index——>%s,%s'%(x,y))
index(1,2)
'''
请输入你的用户名:aaa
请输入你的密码:123
登录成功
index——>1,2
'''
在现实运用过程中,帐号密码是从多个软件或者文件进行读取的,在上述代码中只考虑了一种读取方式(也许是文件读取),还有其他软件中取得帐号密码,比如:数据库、ldap等等。
所以如果数据来源于不同,需要的装饰器代码也许也不同。
在使用wapper函数前,进行一次判断
def auth(func):
def wapper(*args, **kwargs):
name = input('请输入你的用户名:').strip()
pwd = input('请输入你的密码:').strip()
if db_type =='file': #判断数据如果来自文件,执行以下代码
print('基于文件的数据')
res= func(*args, **kwargs)
return res
elif db_type == 'mysql': #判断数据如果来自数据库,执行以下代码
print('基于数据库的数据')
res= func(*args, **kwargs)
return res
elif db_type =='ldap': #判断数据如果来自ldap,执行以下代码
print('基于ldap的数据')
res= func(*args, **kwargs)
return res
return wapper
@auth
def file(x,y):
print('file——>%s,%s'%(x,y))
def mysql(x,y):
print('mysql——>%s,%s'%(x,y))
def ldap(x,y):
print('ldap——>%s,%s'%(x,y))
问题就来了,db_type是一个参数,如何给函数传参?
在wapper函数传参?
def wapper(*args,**kwargs,db_type)
wapper函数的参数是为了被装饰的函数服务,如果在wapper函数加入,被装饰的函数在函数调用阶段也需要更改,这就不符合装饰器原本的作用
在auth函数传参?
def auth(func,db_type):
'''
Traceback (most recent call last):
File "d:\python\study_6_18.py", line 63, in <module>
def file(x,y):
TypeError: auth() missing 1 required positional argument: 'db_type'
'''
此时会报语法错误,在传参的时候少一个参数。
也不行
可以再次运用闭包,将外层再闭包一层函数
def outer(db_type):
def auth(func):
def wapper(*args, **kwargs):
...
return wapper
return auth
@outer('file') #先调用outer('file'),返回了auth的内存地址,之后就变成了@auth——> file=auth(file)
def file(x,y):
print('file——>%s,%s'%(x,y))
@outer('mysql')
def mysql(x,y):
print('mysql——>%s,%s'%(x,y))
@outer('ldap')
def ldap(x,y):
print('ldap——>%s,%s'%(x,y))
mysql(1,2)
'''
请输入你的用户名:aaa
请输入你的密码:123
基于数据库的数据
mysql——>1,2
'''