软件的设计应该遵循开放封闭原则,即对扩展是开放的,而对修改的封闭的。对扩展开饭,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新情况。对修改封闭,意味着对象一旦设计完成,就可以独立完成其工作,而不要对去进行修改。
软件包含的所有功能的源代码以及调用方式,都可以避免修改,否则一旦改错,则极有可能产生连锁反应,最终导致程序崩溃,而对于上线后的软件,新需求或者变化又层出不穷,我们必须为程序提供扩展的可能性,这就用到了装饰器
一。什么是装饰器
装饰:代指为被装饰对象添加新的功能;器:指工具。装饰器与被装饰的对象均可以是任意可调用对象。概况的讲,装饰器的作用就是在不修改被装饰对象源代码和调用方式的前提下为被装饰对象添加额外的功能。装饰器经常用于有切面需求的场景。比如:插入日志,性能测试,事务处理,缓存,权限校验等应用场景,装饰器是解决这类问题的绝佳设计,有了装饰器就可以抽出大量与函数功能本身无关的雷同代码并继续重用。
提示:可调用对象有函数,方法或者类,此处我们单以本章主题函数为例,来介绍函数装饰器,并且被装饰对象也是函数。
二。装饰器的实现
函数装饰器分为:无参装饰器和有参装饰器两种,二者的实现原理一样,都是’函数嵌套+闭包+函数对象‘的组合使用的产物
2.1无参装饰器的实现
为以下函数添加统计其执行时间的功能
import time
def index():
time.sleep(3)
print("welcome to the index page")
return 200
index()
遵循不修改被装饰对象源代码的原则,我们想到的解决办法可能是这样
start_time=time.time()
index()
stop_time=time.time()
print("run time is %s" %(stop_time-start_time))
考虑到还有可能要统计其他函数的执行时间,于是我们将其做成一个单独的工具,函数体需要外部传入被装饰的函数从而进行调用,我们可以使用参数的形式传入
def wapper(func):#(通过参数接收外部的值)
start_time=time.time()
res=func()
# func()
stop_time=time.time()
print("run time is %s" %(stop_time-start_time))
return res
wapper(index)
但是之后函数的调用方式就需要统一改成
wapper(index)
wapper(其他函数)
这便违反了不能修改被装饰对象的调用方式的原则,于是我们换一种为函数体传值的方式,即将值报给函数
def timer(func):
def wrapper(): # 引用外部作用域的变量func
start_time=time.time()
res=func()
stop_time=time.time()
print('run time is %s' %(stop_time-start_time))
return res
return wrapper
这样我们便可以在不修改被装饰函数源代码和调用方式的前提下为其加上统计时间的功能,只不过需要事先执行一次timer将被装饰的函数传入,返回一个布包函数wrapper重新赋值给变量名/函数名index,如下
index=timer(index) #得到index=wrapper,wrapper携带对外作用域的引用:func=原始的index
index() # 执行的是wrapper(),在wrapper的函数体内再执行最原始的index
至此我们便实现了一个无参装饰器timer,可以在不修改被装饰对象index源代码和调用方式的前提下为其加上新功能。但我们忽略了若被装饰的函数是一个有参函数时,便会抛出异常
def home(name):
time.sleep(5)
print('Welcome to the home page',name)
home=timer(home)
home('egon')
#抛出异常
TypeError: wrapper() takes 0 positional arguments but 1 was given
之所以会抛异常,是因为home(‘egon’)调用的其实是wrapper(‘egon’),而函数wrapper没有参数。wrapper函数接收的参数其实是给最原始的func用,为了能满足被装饰函数参数的所有情况,便用上args+*kwargs组合,于是修正装饰器time如下:
def timer(func):
def wrapper(*args,**kwargs):
start_time=time.time()
res=func(*args,**kwargs)
stop_time=time.time()
print("run time is %s" %(stop_time-start_time))
return res
return wrapper
home=timer(home)
home('zhaowei')
此时我们就可以用timer来装饰带参数或不带参数的函数了,但是为了简洁而优雅的使用装饰器,python提供了专门的装饰器语法来取代index=timer(index)的形式,需要在被装饰对象的正上方单独一行添加@timer,当解释器解释到@timer时就会调用timer函数 ,且把他正下方的函数名当做实参传入,然后将返回的结果重新赋值给原函数名
@timer # index=timer(index)
def index():
time.sleep(3)
print('Welcome to the index page')
return 200
@timer # index=timer(home)
def home(name):
time.sleep(5)
print('Welcome to the home page’,name)
如果我们有多个装饰器,可以叠加多个
@deco3
@deco2
@deco1
def index():
pass
叠加多个装饰器也无特殊之处,上述代码语义如下
index=deco3(deco2(deco1(index)))
2.2有参装饰器的实现
了解无参装饰器的实现原理后,我们可以再实现一个用来被装饰对象添加认证功能的装饰器,实现的基本形式如下
def deco(func):
def wrapper(*args,**kwargs):
编写基于文件的认证,认证通过则执行res=func(*args,**kwargs),并返回res
return wrapper
如果我们想提供多种不同的认证方式以供选择,但从wrapper函数的实现角度改写如下:
def deco(func):
def wrapper(*args,**kwargs):
if driver == 'file':
编写基于文件的认证,认证通过则执行res=func(*args,**kwargs),并返回res
elif driver == 'mysql':
编写基于mysql认证,认证通过则执行res=func(*args,**kwargs),并返回res
return wrapper
函数wrapper需要一个driver参数,而函数deco与wrapper的参数都有其特定的功能,不能用来接收其他类别的 参数,可以在deco的外部再包一层auth,用来专门接收额外的参数,这样便保证了在auth函数内无论多少层都可以引用到:
def auth(driver):
def deco(func):
……
return deco
此时我们就实现了一个有参装饰器,使用方式如下
先调用auth_type(driver=‘file’),得到@deco,deco是一个闭包函数,包含了对外部作用域名字driver的引用,@deco的语法意义与无参装饰器一样
def auth(driver):
def deco(func):
def wrapper(*args, **kwargs):
if driver == 'file':
print('file...')
res = func(*args, **kwargs)
elif driver == 'mysql':
print('mysql...')
res = func(*args, **kwargs)
return wrapper
return deco
@auth(driver='file')
def index():
pass
@auth(driver='mysql')
def home():
...
index()#file...
home()#mysql...
可以使用help(函数名)来查看函数的文档注释,本质就是查看函数的doc属性,但对于被装饰之后的函数,查看文档注释
@timer
def home(name):
'''
home page function
:param name: str
:return: None
'''
time.sleep(5)
print('Welcome to the home page',name)
print(help(home))
'''
打印结果:
Help on function wrapper in module __main__:
wrapper(*args, **kwargs)
None
在被装饰之后home=wrapper,查看home.name也可以发现home的函数名确实是wrapper,想要保留原函数的文档和函数名属性,需要修正装饰器
import time
def timer(func):
def wrapper(*args,**kwargs):
start_time=time.time()
res=func(*args,**kwargs)
stop_time=time.time()
print("run time is %s" %(stop_time-start_time))
return res
wrapper.__doc__=func.__doc__
wrapper.__name__=func.__doc__
return wrapper
按照上述方式来实现保留原函数属性过于麻烦,functools模块下提供一个装饰器wraps专门用来帮我们实现这件事,用法如下:
from functools import wraps
import time
def timer(func):
@wraps(func)
def wrapper(*args,**kwargs):
start_time=time.time()
res=func(*args,**kwargs)
stop_time=time.time()
print("run time %s" %(stop_time-start_time))
return res
return wrapper
有参装饰器模板
def 有参装饰器(x,y,z):
def outter(func):
def wrapper(*args,**kwargs):
res=func(*args,**kwargs)
return res
return wrapper
return outter
@有参装饰器(1,y=2,z=3)
def 被装饰对象():
...