python装饰器
开放封闭原则:
软件的设计一般需要遵循开放封闭原则
开放:对软件的功能扩展是开放的,也就是一个软件设计完毕后是可以对其功能进行扩展。
封闭:对软件的源代码是封闭的,也就是当一个函数设计完后,当扩展这个函数功能时不能修改这个函数的源代码以及调用方式。
什么是装饰器?
对其他函数进行装饰的工具,可以定义成函数;
比如:写完一个函数(功能),现在需要给这个函数加一个登录验证,其实也就是扩展函数功能,这里的扩展的功能就可以用装饰器来完成。
装饰器就是在不修改被装饰器对象源代码以及调用方式的前提下为被装饰对象添加新功能
装饰器需求的场景:插入日志、性能测试、事务处理、缓存、权限校验等应用场景
1、无参装饰器
现在有一个需求:测试已经定义好的函数的运时间,相当于为函数添加一个测时功能。
简单方法1:
import time
def func():
time.sleep(2)
start = time.time()
func()
stop = time.time()
print("func函数运行时间:",stop-start) # func函数运行时间: 2.003671646118164
总结:
没有违反开放封闭原则,实现了需求,但是如果函数多次调用,那就每次调用函数是都得多加几行代码,且可能有遗漏,如果是加了一个特别重要的功能,遗漏会导致非常大的损失。
简单方法2:
import time
def func():
start = time.time()
time.sleep(2)
stop = time.time()
print("func函数运行时间:", stop - start) # func函数运行时间: 2.003671646118164
func()
总结:
也实现了需求,且解决简单方法1多次重复代码和可能遗漏问题,但是违反了开放封闭原则。问题:如果需要扩充功能的软件已经上线使用,直接修改源代码可能会出现bug,如果没有及时解决可能会丢失很多用户。
使用装饰器
import time
def outter(func):
def wrapper(*args,**kwargs):
start = time.time()
res = func(*args,**kwargs)
stop = time.time()
print("func函数运行时间:", stop - start) # func函数运行时间: 2.003671646118164
return res
return wrapper
def func():
'''func函数'''
time.sleep(2)
print(func) # <function func at 0x0000024A00E32170>
func = outter(func)
func() # func函数运行时间: 2.014247179031372
print(func) # <function outter.<locals>.wrapper at 0x0000024A6830C5E0>
print(func.__name__) # 查看函数名字:func
print(func.__doc__) # 查看帮助文档:func函数
func() # func函数运行时间: 2.002519369125366
func() # func函数运行时间: 2.013732671737671
func() # func函数运行时间: 2.010169506072998
print(func.__name__) # 查看函数名字:wrapper
print(func.__doc__) # 查看帮助文档:None
总结:
没有违反开放封闭原则,实现了需求,且解决了上面两种方法的缺陷,没有改变func函数的源代码和调用方式,如果新添加能够出现bug,可以直接注释装饰函数,不用花大量时间排出bug。
问题:
没有使用装饰器前函数信息:<function func at 0x0000024A00E32170>;使用后:<function outter..wrapper at 0x0000024A6830C5E0>,其实调用已经发生改变由func变成outter..wrapper,从使用角度来说没有任何变化,但其实已经发生了变化。这个就是装饰器的魅力所在,将闭包函数wrapper无形中伪装成了func函数,并且没有改变func函数的源代码和调用方式,但是又添加了新功能。
为了伪装得更像一些我们对闭包函数进一步伪装
import time
from functools import wraps
def outter(func):
@wraps(func) # 将func的属性全部赋值给wrapper函数
def wrapper(*args,**kwargs):
start = time.time()
res = func(*args,**kwargs)
stop = time.time()
print("func函数运行时间:", stop - start) # func函数运行时间: 2.003671646118164
return res
return wrapper
@outter # @-->python语法糖用法 @outter --> func = outter(func)
def func():
'''func函数'''
time.sleep(2)
func()
print(func) # <function func at 0x00000205C4D0C5E0>
print(func.__name__) # 查看函数名字:func
print(func.__doc__) # 查看帮助文档:func函数
# 使用from functools import wraps模块将wrapper伪装得已经很像了,基本看不出区别
无参装饰器模板
from functools import wraps
def outter(func):
@wraps(func)
def wrapper(*args,**kwargs):
res = func(*args,**kwargs)
return res
return wrapper
2、有参装饰器
语法糖@:
语法糖@帮我们做了什么事
@outter 语法糖@帮我们把被装饰函数func对象当做参数传给装饰器outter,并且从新赋值给func
def func(): 即:func = outter(func)
pass
局限性:语法糖@自动帮我们传参重赋值,但是也带来了很大的局限性,就是装饰器函数只能有一个参数(被装饰函数对象),这时候也就是无参装饰器。但是有些功能扩展需要从外界获得的参数不只有被装饰函数对象。
需求:扩展一个登录验证功能,且不同地方用到的比对数据库不一样。
from functools import wraps
def outter(func):
@wraps(func)
def wrapper(*args,**kwargs):
ip_name = input("请输入账户名:")
ip_pwd = input("请输入密码:")
if ip_name=="xiao" and ip_pwd=="123":
print("假装这是文本数据源比对登录")
res = func(*args,**kwargs)
return res
return wrapper
@outter
def func(): # 需要文本数据源验证登录
pass
@outter
def func1(): # 需要mysql数据源验证登录
pass
总结:用无参函数写的登录验证只能有一种登录验证源,能满足部分需求但不能满足全部。
思考:那我们是不是可以从外界获得一个信息判断一下我们用什么数据源。
if db_yuan == "txt":
if ip_name == "xiao" and ip_pwd == "123":
print("假装这是文本数据源比对登录")
res = func(*args, **kwargs)
return res
elif db_yuan == "mysql":
if ip_name == "xiao" and ip_pwd == "123":
print("假装这是mysql数据源比对登录")
res = func(*args, **kwargs)
return res
else:
print("还不支持改数据源验证登录")
到这就需要考虑db_yuan 怎么获得,由于语法糖@的限定不能给装饰器传更多的参数。前面学过闭包函数,其实闭包函数也就是一种传参方式,那是不是可以用起来。
from functools import wraps
def layer(db_yuan):
def outter(func):
@wraps(func)
def wrapper(*args,**kwargs):
ip_name = input("请输入账户名:")
ip_pwd = input("请输入密码:")
if db_yuan == "txt":
if ip_name == "xiao" and ip_pwd == "123":
print("{}函数,假装这是文本数据源比对登录".format(func.__name__))
res = func(*args, **kwargs)
return res
elif db_yuan == "mysql":
if ip_name == "xiao" and ip_pwd == "123":
print("{}函数,假装这是mysql数据源比对登录".format(func.__name__))
res = func(*args, **kwargs)
return res
else:
print("还不支持该数据源验证登录")
return wrapper
return outter
@layer("txt")
def func(): # 需要文本数据源验证登录
pass
@layer("mysql")
def func1(): # 需要mysql数据源验证登录
pass
func()
func1()
总结:这样就实现了不同地方不同数据源登录;@layer(“txt”)和@layer(“mysql”)帮我们做了什么?
@layer("txt")
layer("txt")是一个函数,并且是函数的调用,@+layer("txt")那肯定是函数的调用优先级高,所以先执行函数layer,
函数layer的返回值是outter函数对象,所以这里的@layer("txt")运行后同等于-->@outter,
outter函数是layer函数的内嵌函数,所以outter函数可以拿到layer函数的参数db_yuan。
总结:无参装饰器是两层嵌套的闭包函数,有参装饰器是三层嵌套的闭包函数
有参装饰器模板
from functools import wraps
def outter(db_yuan):
def inter(func):
@wraps(func)
def wrapper(*args,**kwargs):
res = func()
return res
return wrapper
return inter