裝飾器
一泪电、什么是裝飾器
???????? ”裝飾“代指為被裝飾對(duì)象添加新的功能,“器”代指器具/工具懦鼠,裝飾器與被裝飾的對(duì)象均可以是任意可調(diào)用對(duì)象钻哩。概括地講屹堰,裝飾器的作用就是在不修改被裝飾對(duì)象源代碼和調(diào)用方式的前提下為被裝飾對(duì)象添加額外的功能。裝飾器經(jīng)常用于有切面需求的場(chǎng)景街氢,比如:插入日聲扯键、性能測(cè)試、事務(wù)處理珊肃、緩存荣刑、權(quán)限校驗(yàn)等應(yīng)用場(chǎng)景,裝飾器是解決這類問題的絕佳設(shè)計(jì)伦乔,有了裝飾器厉亏,就可以抽離出大量與函數(shù)功能本身無關(guān)的協(xié)同代碼并繼續(xù)重用。
???????? 可調(diào)用對(duì)象有函數(shù)烈和,方法或者類爱只,此處我們單以本章主題函數(shù)為例,來介紹函數(shù)裝飾器斥杜,并且被裝飾的對(duì)象也是函數(shù)虱颗。
二、為何要用裝飾器
???????? 軟件的設(shè)計(jì)應(yīng)該遵循開放封閉原則蔗喂,即對(duì)擴(kuò)展是開放的忘渔,而對(duì)修改是封閉的。對(duì)擴(kuò)展開放缰儿,意味著有新的需求或變化時(shí)畦粮,可以對(duì)現(xiàn)有代碼進(jìn)行擴(kuò)展,以適應(yīng)新的情況乖阵。對(duì)修改封閉宣赔,意味著對(duì)象一旦設(shè)計(jì)完成,就可以獨(dú)立完成其工作瞪浸,而不要對(duì)其進(jìn)行修改儒将。
三、裝飾器的實(shí)現(xiàn)
函數(shù)飾器分為:無參裝飾器和有參裝飾兩種对蒲,二者的實(shí)現(xiàn)原理一樣钩蚊,都是“函數(shù)嵌套+閉包+函數(shù)對(duì)象”的組合使用的產(chǎn)物。
1.無參裝飾器的實(shí)現(xiàn)
???????? 如果想為下述函數(shù)添加統(tǒng)計(jì)其執(zhí)行時(shí)間的功能
???????? impor time
???????? def index():
?????????????????? time.sleep(3)
?????????????????? print(“welcome to the indexpage”)
?????????????????? return 200
???????? index()
遵循不修改被裝飾對(duì)象對(duì)源代碼的原則蹈矮,我們想到的解決方法可能是這樣
???????? start_time = time.time()
???????? index(x)
???????? stop_time = time.time()
???????? print(“run time is %s”%(stop_time-start_time))
???????? wrapper(index)
???????? wrapper(其他函數(shù))
這便違反了不能修改被裝飾對(duì)象調(diào)用方式的原則砰逻,于是我們換一種為函數(shù)體傳值的方式,即將值包給函數(shù)
def timer(func):
???????? def wrapper():
?????????????????? start_time = time.time()
?????????????????? res = func()
?????????????????? stop_time=time.time()
?????????????????? print(“run time is %s”%(stop_time-start_time))
?????????????????? return res
???????? return wrapper
這倦我們便可以在不修改被裝飾函數(shù)源代碼和調(diào)用方式的前提下為其加上統(tǒng)計(jì)時(shí)間的功能泛鸟,只不過需要事先執(zhí)行一次time將被裝飾的函數(shù)傳入蝠咆,返回一個(gè)閉包函數(shù)wrapper重新賦值給變量名/函數(shù)名index
???????? index = timer(index)
???????? index()
修正裝飾器time如下
def timer(fcun):
???????? def wrpper(*args,**kwargs):
?????????????????? start_time = time.time()
?????????????????? res = fun(*args,**kwargs)
?????????????????? stop_time = time.time()
?????????????????? print(“run time is %s”%(stop_time-start_time))
?????????????????? return res
???????? return wrapper
???????? 為了簡(jiǎn)潔而優(yōu)雅地使用裝飾器python提供了專門的裝飾器語法來取代index=timer(index)的形式,需要在被裝飾對(duì)象的正上方單獨(dú)一行添加@timer北滥,當(dāng)解釋器到@timer時(shí)就會(huì)調(diào)用timer函數(shù)刚操,且把它正下方的函數(shù)名當(dāng)做實(shí)參傳入闸翅,然后將返回的結(jié)果重新賦值給原函數(shù)名
???????? @timer
???????? def index():
?????????????????? time.sleep(3)
?????????????????? print(“welcome to the indexpage”)
?????????????????? return 200
???????? @timer
?????????????????? time.sleep(5)
?????????????????? print(“welcome to the homepage”,name)
???????? 如果我們有多個(gè)裝飾器,可以疊加多個(gè)
???????? @deco3
???????? @deco2
???????? @deco1
???????? def index():
?????????????????? pass
???????? 2.有參裝飾器的實(shí)現(xiàn)
???????? 了解無參裝飾器的實(shí)現(xiàn)原理后赡茸,我們可以再實(shí)現(xiàn)一個(gè)用來為被裝飾對(duì)象添加認(rèn)證功能的裝飾器缎脾,實(shí)現(xiàn)的基本形式如下
???????? def deco(func):
?????????????????? def wrapper(*args,**kwargs):
??????????????????????????? if driver =? “file”:
???????????????????????????????????? 編寫基于文件的認(rèn)證,認(rèn)證通過則執(zhí)行res=fun(*args,**kwargs),并返回???????????????????????????????? res
??????????????????????????? elif driver == “mysql”:
?????????????????? retun wrapper
函數(shù)wrapper需要一個(gè)driver參數(shù)占卧,而函數(shù)deco與wrapper的參數(shù)都有其特定的功能遗菠,不能用來接受其他類別的參數(shù),可以在deco的外部再包一層函數(shù)auth,用來專門接受處的參數(shù)华蜒,這樣便保證了在auth函數(shù)內(nèi)無論多少層都可以引用到
此時(shí)我們就實(shí)現(xiàn)了一個(gè)有參裝飾器
先調(diào)用auth_type(driver=”file’),得到@deco,deco是個(gè)閉包函數(shù)
包含了對(duì)外部作用域名字driver的引用辙纬,@deco的語法意義與無參裝飾器一樣
@auth(driver=”file”)
Def index():
???????? Pass
@auth(driver=”mysql”)
Def home():
???????? Pass
可以使用help(函數(shù)名)來查看函數(shù)的文檔注釋,本質(zhì)就是查看函數(shù)的doc屬性叭喜,但對(duì)于被裝飾之后的函數(shù)贺拣,查看文檔注釋
@timer
def home(name):
???????? time.sleep(5)
???????? print(“welcome to the home page”,name)
print(help(home))
在被裝飾之后home = wrapper,查看home.name也可以發(fā)現(xiàn)home的函數(shù)名確實(shí)是wrapper,想要保留原函數(shù)的文檔和函數(shù)名屬性,需要修正裝飾器
def timer(func):
???????? def wrapper(*argw,**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._name_
???????? return wrapper
按照上述方式來實(shí)現(xiàn)保留原函數(shù)屬性過于麻煩捂蕴,functools模塊下提供一個(gè)裝飾器wraps專門用來幫我們實(shí)現(xiàn)這件事
???????? from functools import wraps
???????? def timer(func):
?????????????????? @wraps(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