# 這是一個裝飾器的簡單例子
@dec
def func():
pass
什么是裝飾器
裝飾器可以在不改變原有對象的代碼及調用方式的情況下,為原有的對象增加新的功能或限制條件。裝飾器有函數(shù)裝飾器,也有類裝飾器面哥。裝飾器體現(xiàn)的是開放封閉原則,即對功能擴展的開放毅待,對修改已實現(xiàn)功能的封閉尚卫。
裝飾器本質上就是Python的函數(shù),它和普通的函數(shù)沒有任何區(qū)別尸红,只是有了特殊的用法吱涉,所以在特殊用法下就有了裝飾器這個名字。
裝飾器怎么用
現(xiàn)在有一個hello函數(shù)外里,功能很簡單怎爵,只是打印hello world!
def hello():
print('hello world!')
hello()
它的執(zhí)行結果為:
hello world!
現(xiàn)在要對這個函數(shù)進行一下功能的擴展盅蝗。
def dec(func): # 這個就是裝飾器鳖链,參數(shù)就是被裝飾的函數(shù)
def wrapper():
print('start...')
func()
print('end...')
return wrapper
@dec # 這是裝飾器的用法
def hello():
print('hello world!')
hello() #函數(shù)的調用方法和上邊一樣沒有改變
現(xiàn)在的執(zhí)行結果為:
start...
hello world!
end...
如上面的代碼顯示,函數(shù)dec就是裝飾器墩莫,函數(shù)hello就是被裝飾的函數(shù)芙委。而裝飾器的用法,就是hello函數(shù)上面的@dec狂秦。注意灌侣,這里應用裝飾器只是寫了dec這個函數(shù)名,而不是dec()這樣調用它故痊。
裝飾器的運行機制
還是上面的例子顶瞳。
- 解釋器讀到函數(shù)dec,也就是裝飾器愕秫。這時它還沒有被調用,所以只是被存在了內(nèi)存中焰络,并沒有被執(zhí)行戴甩。
- 到了@dec。@dec和dec()一樣闪彼,都會執(zhí)行這個函數(shù)甜孤,但是不同的是协饲,@dec會把被裝飾的hello函數(shù)的本身(而不是函數(shù)執(zhí)行結果)當作參數(shù),傳入到dec函數(shù)中缴川,相當于執(zhí)行了dec(hello)這個函數(shù)茉稠。
- 這時開始執(zhí)行dec函數(shù)。首先會把wrapper函數(shù)存入到內(nèi)存中(并不是執(zhí)行)把夸,然后返回wrapper函數(shù)本身(也不是執(zhí)行)給hello函數(shù)而线。也就是,hello函數(shù)被裝飾完之后恋日,內(nèi)存地址被指向到了wrapper函數(shù)的內(nèi)存地址膀篮。
- 執(zhí)行hello函數(shù)。由于hello函數(shù)的地址已經(jīng)被指向到了wrapper函數(shù)岂膳,所以執(zhí)行結果就是wrapper函數(shù)的執(zhí)行結果誓竿。wrapper函數(shù)中的func這個函數(shù)地址才是原來hello函數(shù)的內(nèi)存地址。所以使用裝飾器之后谈截,表面上不會改變原來函數(shù)調用的方法筷屡。
以上就是裝飾器的運行機制。
為什么是嵌套函數(shù)
剛開始學習裝飾器的人可能會比較疑惑簸喂,裝飾器為什么要用嵌套函數(shù)毙死?感覺好多功能不用嵌套也可以實現(xiàn)。下面看例子娘赴。
def dec(func):
print('start...')
func()
print('end...')
@dec
def hello():
print('hello world!')
執(zhí)行結果為:
start...
hello world!
end...
這個執(zhí)行結果看起來沒有問題规哲。是的,執(zhí)行結果是沒有問題诽表,但是問題是還沒有寫執(zhí)行語句卻已經(jīng)有了執(zhí)行結果唉锌。
造成這個問題的原因就在@dec這里。@dec就是執(zhí)行裝飾器的語句竿奏,所以它就執(zhí)行了dec這個函數(shù)袄简。這就是為什么,還沒有寫執(zhí)行語句卻有了執(zhí)行結果的原因泛啸。這也是為什么裝飾器要用嵌套函數(shù)的原因绿语,再用一個函數(shù)封裝一下,避免在定義階段就給執(zhí)行了候址。
被裝飾的函數(shù)帶參數(shù)
def hello(string):
print(f'hello {string}!')
hello('python')
執(zhí)行結果為:
hello python!
這是一個帶參數(shù)的函數(shù)吕粹,那么這種函數(shù)如何添加裝飾器呢?
其實岗仑,只要理解了被裝飾的函數(shù)會被指向到裝飾器里的函數(shù)匹耕,那么在裝飾器里的函數(shù)加上參數(shù)就解決了傳參的問題。下面看例子荠雕。
def dec(func):
def wrapper(string): # 添加和hello一樣的參數(shù)即可
print('start...')
func(string) # 通過wrapper函數(shù)傳遞來的參數(shù)
print('end...')
return wrapper
@dec
def hello(string): # 內(nèi)存地址會被指向wrapper的內(nèi)存地址
print(f'hello {string}!')
hello('python')
執(zhí)行的結果為:
hello python!
裝飾器帶參數(shù)
如果理解了前邊的內(nèi)容稳其,那么理解裝飾器帶參數(shù)也就比較容易了驶赏。下面先看例子。
def outer(name): # 多了一層函數(shù)既鞠,用于接收傳遞裝飾器的參數(shù)
def dec(func):
def wrapper(string):
print(f'{name} start...')
func(string)
print(f'{name} end...')
return wrapper
return dec
@outer('haha') # 裝飾器執(zhí)行語句多了括號和參數(shù)
def hello(string):
print(f'hello {string}!')
hello('python')
執(zhí)行結果為:
haha start...
hello python!
haha end...
首先可以看到煤傍,裝飾器在定義的時候又多了一層嵌套,這層嵌套用于接收傳遞參數(shù)嘱蛋,并且返回下一層的嵌套函數(shù)蚯姆。
然后可以看到裝飾器多了括號和參數(shù)『荆現(xiàn)在說的就是帶參數(shù)的裝飾器共啃,所以帶參數(shù)沒有什么稀奇的。
最主要的是多的那個括號夫嗓。裝飾器執(zhí)行語句多了括號之后桐玻,它的意思也稍微有了變化篙挽。我們知道,執(zhí)行函數(shù)就是函數(shù)名加上括號镊靴。那么 @outer('haha') 的執(zhí)行順序是下面這樣的铣卡。
- 先執(zhí)行outer('haha')這個函數(shù),這時它的參數(shù)是'haha'偏竟。
- outer函數(shù)返回值是dec煮落,所以@outer('haha') = @dec,并把參數(shù)傳到了函數(shù)內(nèi)踊谋。
- @dec這個語句就是我們前邊熟悉的裝飾器的執(zhí)行語句了蝉仇。它會把hello函數(shù)本身當作參數(shù)傳遞給函數(shù)dec,并把內(nèi)存地址指向到了wrapper函數(shù)殖蚕。后邊執(zhí)行過程就跟前邊的例子一樣轿衔,也就不再贅述了。
主要知道@outer()會先執(zhí)行outer函數(shù)睦疫,而不是把hello當作參數(shù)的裝飾器語句害驹,那么對于帶參數(shù)的裝飾器也就沒什么難點了。
疊加裝飾器
裝飾器也可以疊加使用蛤育,還是先看例子宛官。
def dec1(func):
print('這是第一層')
def wrapper():
print('這是第1層開始')
func()
print('這是第1層結束')
return wrapper
def dec2(func):
print('這是第二層')
def wrapper():
print('這是第2層開始')
func()
print('這是第2層結束')
return wrapper
@dec2
@dec1
def hello():
print('hello world!')
print('開始')
hello()
執(zhí)行結果為:
這是第一層
這是第二層
開始
這是第2層開始
這是第1層開始
hello world!
這是第1層結束
這是第2層結束
這個結果可能剛開始看有些不那么好理解,我們慢慢看瓦糕。
先看執(zhí)行結果的前三行底洗。這三行內(nèi)容里,首先執(zhí)行的是兩個裝飾器的內(nèi)容咕娄,之后才是print語句枷恕。從這個執(zhí)行結果可以看出,python是從上到下順序執(zhí)行谭胚,并在遇到裝飾器執(zhí)行語句的時候會自動執(zhí)行裝飾器函數(shù)('第幾層開始'沒有跟著執(zhí)行是因為被封裝進了函數(shù)徐块,要執(zhí)行語句才能執(zhí)行)。再從首先執(zhí)行了第一層又執(zhí)行了第二層可以看出灾而,遇到疊加裝飾器時是從下向上執(zhí)行的胡控。
明白了裝飾器是從下向上執(zhí)行,那么就是下一層裝飾器的函數(shù)就是它上一層裝飾器的參數(shù)旁趟。
@dec2 # 相當與dec2(dec1(hello))
@dec1 # 相當于dec1(hello)
def hello():
這么看這個執(zhí)行結果就好理解了昼激。
結束
這篇文章主要是幫助初步理解裝飾器和一些簡單的用法,在這里就不詳細說明其他更深奧的用法了(主要是我不會)锡搜,那么這篇文章也就到這里了橙困。