目錄:http://m.itdecent.cn/p/863c446364a8
裝飾器
我們談裝飾器之前先來(lái)說(shuō)說(shuō)開(kāi)放封閉原則。
開(kāi)放封閉原則:
1.開(kāi)放---對(duì)代碼的拓展開(kāi)放? ? ? ? ? 2.封閉---對(duì)源碼的修改封閉
接著我們?cè)賮?lái)談裝飾器,什么是裝飾器?
裝飾器是在不改變?cè)瘮?shù)的代碼以及調(diào)用方式的前提下,為其增加新的功能。
裝飾器完全遵守開(kāi)放封閉原則。
裝飾器同樣擁有三前提:作用域、高階函數(shù)、閉包。學(xué)會(huì)這三個(gè)東西再來(lái)學(xué)裝飾器將是水到渠成。
讓我們來(lái)做一個(gè)銀行存款的例子。
def a():
? ? print("-----存款中-----")
a()
我們現(xiàn)在有一個(gè)需求,給他增加一個(gè)密碼驗(yàn)證的過(guò)程。
def a():
? ? print("-----存款中-----")
def b():
????print("-----密碼驗(yàn)證中-----")
b()
a()
或者:
def a():
? ??b()
? ? print("-----存款中-----")
def b():
????print("-----密碼驗(yàn)證中-----")?
a()
這顯然違背了開(kāi)放封閉原則,而且造成了代碼冗余。
現(xiàn)在我們對(duì)它進(jìn)行第一次優(yōu)化:
這一次優(yōu)化是將存款函數(shù)完全放到密碼驗(yàn)證函數(shù)的保護(hù)之中,所以密碼驗(yàn)證函數(shù)要接受存款函數(shù)。
即? 我們這些優(yōu)化的思路是:使用高階函數(shù)來(lái)封裝a函數(shù),以來(lái)避免對(duì)a函數(shù)的修改。
def a():
? ? print("-----存款中-----")
def b(pwd):? # pwd = a
????print("-----密碼驗(yàn)證中-----")?
? ? pwd()? ? ?#a()??
b(a)??
這種寫法只是沒(méi)有改變?cè)瘮?shù)代碼,并且增加了新功能。但是他改變了函數(shù)的調(diào)用方式,這就不符合我們的開(kāi)放封閉原則。
我們下面進(jìn)行第二次優(yōu)化:
這次我們使用閉包來(lái)對(duì)我們的函數(shù)進(jìn)行優(yōu)化,這樣我們就可以遵循開(kāi)放封閉原則的前提下,完成功能的增加。這就是一個(gè)初級(jí)裝飾器。
def a():
? ? print("-----存款中-----")def? b(pwd):? ?#pwd=a
????def inner():
? ? ? ? print("-----密碼驗(yàn)證中-----")
? ? ????pwd()? ? ? ? ? ? # pwd()=a()
? ? return inner? ? ? ? ? #返回inner函數(shù)
a=b(a)? ? ? ? ? ? ? #inner函數(shù)返回到這里,即a=inner
a()? ? ? ? ? #a()=inner(),這里是對(duì)inner的調(diào)用
? ? 接著我們來(lái)細(xì)致剖析一下初級(jí)裝飾器的運(yùn)行過(guò)程。
1.第一次讀取:讀取a函數(shù)的整體。
2.第二次讀取? :讀取b函數(shù)的整體
3.接著開(kāi)始執(zhí)行? a = b(a)? 將 a函數(shù) 帶入到 b函數(shù) 內(nèi),即 def b(a)? 此時(shí) 在b函數(shù)內(nèi)? pwd=a
4.因?yàn)?b函數(shù) 接受到了參數(shù),系統(tǒng)開(kāi)始執(zhí)行 b函數(shù) :b函數(shù)內(nèi)只做了兩件事情? 定義了一個(gè)
inner函數(shù),將inner函數(shù)返回。
5.? a=b(a)=inner? 因?yàn)?b(a) 返回了 inner,所以 現(xiàn)在在函數(shù)外 a=inner
6.接著執(zhí)行了? a()? 調(diào)用了 a 函數(shù)? 此時(shí)? 因?yàn)?a=inner,所以a()=inner()
7.開(kāi)始執(zhí)行? inner():? inner()函數(shù)干了兩件事情 1.print("-----密碼驗(yàn)證中-----") 2.pwd()? 因?yàn)?/p>
pwd=a? 所以 pwd() = a()
8.開(kāi)始調(diào)用最開(kāi)始的a函數(shù), 執(zhí)行 print("-----存款中-----")
9.執(zhí)行完成。
其實(shí)初級(jí)裝飾器還可以在優(yōu)化一下:
將初級(jí)裝飾器的調(diào)用語(yǔ)句換成內(nèi)置調(diào)用方式(語(yǔ)法糖)。
def b(pwd):? ? ? ? ?#這時(shí)將a傳遞給b函數(shù)中的pwd 即pwd=a
????def inner():
????????print("-----密碼驗(yàn)證中-----")
? ? ? ? pwd()
? ? return inner? ? ? ? #返回inner函數(shù)
@b? ? ?#這種寫法就是語(yǔ)法糖,他就相當(dāng)于是a=b(a)? ? ? ? #inner函數(shù)返回給了a
def a ():
? ? print("-----存款中-----")
a()? ? ? ? ?#a函數(shù)的調(diào)用相當(dāng)于inner函數(shù)的調(diào)用
?接著我們來(lái)細(xì)致剖析一下語(yǔ)法糖裝飾器的運(yùn)行過(guò)程。
1.第一次讀取 :讀取b函數(shù)的整體。
2.第二次讀取? :? ?因?yàn)樽x取到了? @b, 所以系統(tǒng)接下來(lái)的兩行看成整體,即:a = b(a)? ?def a():
3.系統(tǒng)開(kāi)始執(zhí)行? a = b(a)? 將 a函數(shù) 帶入到 b函數(shù) 內(nèi),即: def b(a)? 此時(shí) 在b函數(shù)內(nèi)? pwd=a
4.因?yàn)?b函數(shù) 接受到了參數(shù),系統(tǒng)開(kāi)始執(zhí)行 b函數(shù) :? b函數(shù)內(nèi)只做了兩件事情? 定義了一個(gè) inner函
數(shù),將inner函數(shù)返回。
5.? a=b(a)=inner? 因?yàn)?b(a) 返回了 inner,所以 現(xiàn)在在函數(shù)外 a=inner
6.最后執(zhí)行了? a()? 調(diào)用了 a函數(shù)? 此時(shí)? 因?yàn)?a=inner? a()=inner()
7.開(kāi)始執(zhí)行? inner():? inner()函數(shù)干了兩件事情 1.print("-----密碼驗(yàn)證中-----")?2.pwd()? 因?yàn)?/p>
pwd=a? 所以 pwd() = a()
8.開(kāi)始調(diào)用最開(kāi)始的a函數(shù), 執(zhí)行?print("-----存款中-----")
9.執(zhí)行完成。
裝飾器的返回值
現(xiàn)在我們又有了新的需求,我們想要獲取存款的過(guò)程,而不是將存款過(guò)程直接打印出來(lái),這就需要我們的存款函數(shù)a有返回值,現(xiàn)在修改如下:
def b(pwd):
? ? def inner():
? ? ? ? print("-----密碼驗(yàn)證中-----")
? ? ? ? r=pwd()? ? ? ? ?#這里本來(lái)是直接調(diào)用a函數(shù),現(xiàn)在將他修改為接收a函數(shù)的返回值
? ? ? ? return r? ? #這里將函數(shù)a的返回值返回給最后一句a的調(diào)用
? ? return inner? ?
@b? ?
def a ():? ? ? ? ? ??
????return "-----存款中,存款金額為200-----"? ? ? #a函數(shù)的返回值
print(a())? ? ? #這里是a的調(diào)用,同時(shí)是a函數(shù)返回值的最后一站
帶參裝飾器
上面是我們普通裝飾器,不能夠傳遞參數(shù),靈活性較差,所以就有了我們的帶參裝飾器,讓我們?cè)谏厦娴睦友a(bǔ)充一個(gè)新功能可以顯示我們的存款金額。
def b(pwd):? ? ? ? ?#這時(shí)將a傳遞給b函數(shù)中的pwd 即pwd=a(money)
????def inner(*args,**kwargs):? #讓inner使用不定長(zhǎng)參數(shù)來(lái)接收a的參數(shù)? ?*args=(500)
????????print("-----密碼驗(yàn)證中-----")
? ? ? ? pwd(*args,**kwargs)? ? ? ? ? ? #在這里將500賦給pwd,相當(dāng)于是a(500)
? ? return inner? ? ? ? #返回inner函數(shù)
@b? ? ?#這種寫法就是語(yǔ)法糖,他就相當(dāng)于是a=b(a)? ? ? ? #inner函數(shù)返回給了a
def a (money):? ? ? ? ?#此時(shí)money=500
? ? print("-----存款中,存款金額為{}-----".format(money))
a(500)? ? ? ? ?#a函數(shù)的調(diào)用相當(dāng)于inner函數(shù)的調(diào)用
運(yùn)行結(jié)果為:
-----密碼驗(yàn)證中-----
-----存款中,存款金額為500-----
帶參語(yǔ)法糖?
現(xiàn)在我們?cè)俳o他美化一下,在密碼驗(yàn)證中之后輸出一排*,同時(shí)我們又要遵守我們的開(kāi)放封閉原則,所以要使用帶參語(yǔ)法糖,修改如下:
def c(char):? ? ? ? ? ? ? ?#這時(shí)將*傳遞給c函數(shù)的char即char=*
????def b(pwd):? ? ? ? ?#這時(shí)將a傳遞給b函數(shù)中的pwd 即pwd=a
????????def inner():
????????????print("-----密碼驗(yàn)證中-----")
????????????print(char*15)? ?#char = “*”? 輸出 ***************
? ? ? ????? pwd()? ? ? ? ? ? ? ? ? ? #pwd = a? ?這里調(diào)用了a函數(shù) 輸出? ?-----存款中-----
? ????? return inner? ? ? ? ? ? ? #將inner函數(shù)返回給a=b(a)
????return b? ? ? ? ? ? ? ? ? ? ? ? ?#將b函數(shù)返回給a=c(*)
@c("*")? ? ? ? ? ? #在這里相當(dāng)于執(zhí)行了兩條語(yǔ)句 a=c(*)? ?a=b(a)? ?這里a接受到了 兩個(gè)返回值,后一個(gè)返回值? innner? 覆蓋前一個(gè)返回值 b? 即? a=inner
def a ():
? ? print("-----存款中-----")
a()? ? ? ? ?#a函數(shù)的調(diào)用相當(dāng)于inner函數(shù)的調(diào)用
運(yùn)行結(jié)果為:
-----密碼驗(yàn)證中-----
***************
-----存款中-----
標(biāo)準(zhǔn)版的裝飾器
def wrapper(f):
? ? def inner(*args,**kwargs):
? ? ? ? f(*args,**kwargs)
? ? return inner
設(shè)計(jì)一個(gè)裝飾器,顯示函數(shù)運(yùn)行的時(shí)間。 輸出格式為(函數(shù)名,時(shí)間)
import time
def timmer(f):
? ? def inner ():
? ? ? ? start_time=time.time()
? ? ? ? f()
? ? ? ? end_time=time.time()
? ? ? ? print(f,end_time-start_time)
? ? return inner
@timmer? fun=timmer(fun)
def fun():
? ? time.sleep(2)
? ? print("Welcome to China")
fun()