1.概念:
裝飾器是一個很著名的設(shè)計(jì)模式,經(jīng)常被用于有切面需求的場景,較為經(jīng)典的應(yīng)用有插入日志、增加計(jì)時邏輯來檢測性能、加入事務(wù)處理等。裝飾器是解決這類問題的絕佳設(shè)計(jì),有了裝飾器,我們就可以抽離出大量函數(shù)中與函數(shù)功能本身無關(guān)的雷同代碼并繼續(xù)重用。概括的講,裝飾器的作用就是為已經(jīng)存在的對象添加額外的功能。
- 裝飾器應(yīng)用的由來:
裝飾器的定義很是抽象,我們來看一個小例子。
先定義一個簡單的函數(shù):
def foo():
print 'in foo()'
foo()
然后呢,我想看看執(zhí)行這個函數(shù)用了多長時間,好吧,那么我們可以這樣做:
import time
def foo():
start = time.clock()
print 'in foo()'
end = time.clock()
print 'used:', end - start
foo()
很好,功能看起來無懈可擊。但是如果我要看看另外一個函數(shù)的執(zhí)行性能,計(jì)算時間的這些與函數(shù)本身功能無關(guān)的代碼我還得寫一遍,這很不軟件工程啊,怎么辦呢?
還記得嗎,函數(shù)在Python中是一等公民,那么我們可以考慮重新定義一個函數(shù)timeit,將foo的引用傳遞給他,然后在timeit中調(diào)用foo并進(jìn)行計(jì)時,這樣,我們就達(dá)到了不改動foo定義的目的
import time
def foo():
print 'in foo()'
def timeit(func):
start = time.clock()
func()
end =time.clock()
print 'used:', end - start
timeit(foo)
看起來邏輯上并沒有問題,一切都很美好并且運(yùn)作正常!但是,我們似乎修改了調(diào)用部分的代碼。原本我們是這樣調(diào)用的:foo(),修改以后變成了:timeit(foo)。這樣的話,如果foo在N處都被調(diào)用了,你就不得不去修改這N處的代碼?;蛘吒鼧O端的,考慮其中某處調(diào)用的代碼無法修改這個情況,比如:這個函數(shù)是你交給別人使用的。
既然如此,我們就來想想辦法不修改調(diào)用的代碼;如果不修改調(diào)用代碼,也就意味著調(diào)用foo()需要產(chǎn)生調(diào)用timeit(foo)的效果。我們可以想到將timeit賦值給foo,但是timeit似乎帶有一個參數(shù)……想辦法把參數(shù)統(tǒng)一吧!如果timeit(foo)不是直接產(chǎn)生調(diào)用效果,而是返回一個與foo參數(shù)列表一致的函數(shù)的話……就很好辦了,將timeit(foo)的返回值賦值給foo,然后,調(diào)用foo()的代碼完全不用修改!
import time
def foo():
print 'in foo()'
定義一個計(jì)時器,傳入一個,并返回另一個附加了計(jì)時功能的方法
def timeit(func):
# 定義一個內(nèi)嵌的包裝函數(shù),給傳入的函數(shù)加上計(jì)時功能的包裝
def wrapper():
start = time.clock()
func()
end =time.clock()
print 'used:', end - start
# 將包裝后的函數(shù)返回
return wrapper
foo = timeit(foo)#傳入的參數(shù)foo為原函數(shù)名,變量foo為新變量不是原函數(shù)
foo()
這樣,一個簡易的計(jì)時器就做好了!我們只需要在定義foo以后調(diào)用foo之前,加上foo = timeit(foo),就可以達(dá)到計(jì)時的目的,這也就是裝飾器的概念,看起來像是foo被timeit裝飾了。
這樣,一個簡易的計(jì)時器就做好了!我們只需要在定義foo以后調(diào)用foo之前,加上foo = timeit(foo),就可以達(dá)到計(jì)時的目的,這也就是裝飾器的概念,看起來像是foo被timeit裝飾了。
在這個例子中,函數(shù)進(jìn)入和退出時需要計(jì)時,這被稱為一個橫切面(Aspect),這種編程方式被稱為面向切面的編程(Aspect-Oriented Programming)。與傳統(tǒng)編程習(xí)慣的從上往下執(zhí)行方式相比較而言,像是在函數(shù)執(zhí)行的流程中橫向地插入了一段邏輯。在特定的業(yè)務(wù)領(lǐng)域里,能減少大量重復(fù)代碼。面向切面編程還有相當(dāng)多的術(shù)語,這里就不多做介紹,感興趣的話可以去找找相關(guān)的資料。
這個例子僅用于演示,并沒有考慮foo帶有參數(shù)和有返回值的情況,完善它的重任就交給讀者你最為練習(xí) ,可以檢驗(yàn)下你是否真正理解了學(xué)會了。
上面這段代碼看起來似乎已經(jīng)不能再精簡了,Python于是提供了一個語法糖來降低字符輸入量
import time
def timeit(func):
def wrapper():
start = time.clock()
func()
end =time.clock()
print 'used:', end - start
return wrapper
@timeit
def foo():
print 'in foo()'
foo()
重點(diǎn)關(guān)注第11行的@timeit,在定義上加上這一行與另外寫foo = timeit(foo)完全等價(jià),千萬不要以為@有另外的魔力。除了字符輸入少了一些,還有一個額外的好處:這樣看上去更有裝飾器的感覺。
函數(shù)引用;
要理解python的裝飾器,我們首先必須明白在Python中函數(shù)也是被視為對象。這一點(diǎn)很重要。先看一個例子:
def shout(word="yes") :
return word.capitalize()+" !"
print shout()
輸出 : 'Yes !'
作為一個對象,你可以把函數(shù)賦給任何其他對象變量
scream = shout
注意我們沒有使用圓括號,因?yàn)槲覀儾皇窃谡{(diào)用函數(shù)
我們把函數(shù)shout賦給scream,也就是說你可以通過scream調(diào)用shout
print scream()
輸出 : 'Yes !'
還有,你可以刪除舊的名字shout,但是你仍然可以通過scream來訪問該函數(shù)
del shout
try :
print shout()
except NameError, e :
print e
#輸出 : "name 'shout' is not defined"
print scream()
輸出 : 'Yes !'
我們暫且把這個話題放旁邊,我們先看看python另外一個很有意思的屬性:可以在函數(shù)中定義函數(shù):
def talk() :
# 你可以在talk中定義另外一個函數(shù)
def whisper(word="yes") :
return word.lower()+"...";
# ... 并且立馬使用它
print whisper()
你每次調(diào)用'talk',定義在talk里面的whisper同樣也會被調(diào)用
talk()
輸出 :
yes...
但是"whisper" 不會單獨(dú)存在:
try :
print whisper()
except NameError, e :
print e
#輸出 : "name 'whisper' is not defined"*
從以上兩個例子我們可以得出,函數(shù)既然作為一個對象,因此:
其可以被賦給其他變量
其可以被定義在另外一個函數(shù)內(nèi)
這也就是說,函數(shù)可以返回一個函數(shù),看下面的例子:
def getTalk(type="shout") :
# 我們定義另外一個函數(shù)
def shout(word="yes") :
return word.capitalize()+" !"
def whisper(word="yes") :
return word.lower()+"...";
# 然后我們返回其中一個
if type == "shout" :
# 我們沒有使用(),因?yàn)槲覀儾皇窃谡{(diào)用該函數(shù)
# 我們是在返回該函數(shù)
return shout
else :
return whisper
然后怎么使用呢 ?
把該函數(shù)賦予某個變量
talk = getTalk()
這里你可以看到talk其實(shí)是一個函數(shù)對象:
print talk
輸出 : <function shout at 0xb7ea817c>
該對象由函數(shù)返回的其中一個對象:
print talk()
或者你可以直接如下調(diào)用 :
print getTalk("whisper")()
輸出 : yes...
<p></p>
還有,既然可以返回一個函數(shù),我們可以把它作為參數(shù)傳遞給函數(shù):
def doSomethingBefore(func) :
print "I do something before then I call the function you gave me"
print func()
doSomethingBefore(scream)
輸出 :
I do something before then I call the function you gave me
Yes !
這里你已經(jīng)足夠能理解裝飾器了,其他它可被視為封裝器。也就是說,它能夠讓你在裝飾前后執(zhí)行代碼而無須改變函數(shù)本身內(nèi)容。