新聞中心
1. 疊加使用Python裝飾器

最近有學(xué)員問(wèn),Python中也有與Java類似的@xxxx語(yǔ)法,這到底是什么意思呢?現(xiàn)在我就來(lái)回答這個(gè)問(wèn)題。
Java中的@xxxx語(yǔ)法是注解(Annotation),而Python中的@xxxx語(yǔ)法是裝飾器(decorator),盡管在語(yǔ)法上類似,但作用完全不同。Java的注解相當(dāng)于語(yǔ)法元素(方法、類、接口等)的元數(shù)據(jù)。而Python的裝飾器是對(duì)Python函數(shù)(方法)的包裝,現(xiàn)在我們來(lái)舉個(gè)例子。
- @makebold
- @makeitalic
- def say():
- return "Hello"
- print(say()))
這段代碼,對(duì)函數(shù)say使用了2個(gè)裝飾器:@makebold和@makeitalic,而且是疊加狀態(tài)。@makeitalic會(huì)首先作用于say函數(shù),然后@makebold會(huì)作用于@makeitalic裝飾器的結(jié)果,這兩個(gè)裝飾器分別用...和...包裝say函數(shù)返回的字符串,所以這段代碼的執(zhí)行結(jié)果如下:
Hello
不過(guò)直接執(zhí)行這段代碼肯定會(huì)出錯(cuò)的,這是因?yàn)檫@兩個(gè)裝飾器還沒(méi)定義,下面就看下如何定義這兩個(gè)裝飾器。
2. 定義Python裝飾器
裝飾器本身就是一個(gè)普通的Python函數(shù),只是函數(shù)的參數(shù)需要是函數(shù)類型(通常傳入被裝飾的函數(shù)),定義形式如下:
- Hello
現(xiàn)在就來(lái)定義前面給出的兩個(gè)裝飾器:
- from functools import wraps
- def makebold(fn):
- @wraps(fn)
- def makebold_wrapped(*args, **kwargs):
- return "" + fn(*args, **kwargs) + ""
- return makebold_wrapped
- def makeitalic(fn):
- @wraps(fn)
- def makeitalic_wrapped(*args, **kwargs):
- return "" + fn(*args, **kwargs) + ""
- return makeitalic_wrapped
很明顯,makebold和makeitalic是兩個(gè)普通的Python函數(shù),而且在函數(shù)內(nèi)部分別定義了另外兩個(gè)函數(shù),而且這兩個(gè)函數(shù)被作為返回值返回。這其中使用了wraps函數(shù),這個(gè)函數(shù)其實(shí)可以不加,不過(guò)會(huì)有一些副作用。
由于使用@makebold和@makeitalic修飾某個(gè)函數(shù)時(shí),會(huì)將這個(gè)被修飾的函數(shù)傳入makebold函數(shù)和makeitalic函數(shù),也就是說(shuō),fn參數(shù)就是這個(gè)被修飾的函數(shù)。而在外部調(diào)用這個(gè)被修飾函數(shù)時(shí),實(shí)際上是調(diào)用了修飾器返回的函數(shù),也就是makebold_wrapped和makeitalic_wrapped,這樣就會(huì)導(dǎo)致被修飾函數(shù)屬性的改變,如函數(shù)名、函數(shù)文檔等,現(xiàn)在可以先去掉@wraps,執(zhí)行下面的代碼:
- @makeitalic
- @makebold
- def say():
- return "Hello"
- print(say.__name__) # 輸出函數(shù)名
會(huì)輸出如下的內(nèi)容:
- makebold_wrapped
由于最后使用了@makebold裝飾器,所以輸出的是makebold函數(shù)返回的makebold_wrapped函數(shù)的名字。如果加上@wraps,那么就會(huì)輸出say。
要注意,需要通過(guò)裝飾器方式調(diào)用wraps函數(shù),這樣其實(shí)就相當(dāng)于在@makebold外面又包了一層裝飾器(wraps)。
3. 理解Python函數(shù)
現(xiàn)在我們已經(jīng)了解了如何自定義Python裝飾器,但應(yīng)該如何理解裝飾器呢?到底是什么原理呢?要想理解Python裝飾器,首先應(yīng)該知道Python函數(shù)就是對(duì)象,看下面的例子:
- def shout(word="yes"):
- return word.capitalize()
- # 輸出:Yes
- print(shout())
- # 將shout函數(shù)賦給另一個(gè)變量,這里并沒(méi)有使用圓括號(hào),
- # 所以不是調(diào)用函數(shù),而是將函數(shù)賦給另一個(gè)變量,也就是為函數(shù)起一個(gè)別名
- scream = shout
- # 可以用scream調(diào)用shout函數(shù)
- # 輸出:Yes
- print(scream())
- # 目前,同一個(gè)函數(shù),有兩個(gè)引用:scream和shout,可以使用del刪除一個(gè)引用
- del shout
- try:
- # 該引用刪除后,就不能通過(guò)該引用調(diào)用函數(shù)了
- print(shout())
- except NameError as e:
- print(e)
- # 仍然可以通過(guò)另外一個(gè)引用調(diào)用函數(shù)
- # 輸出:Yes
- print(scream())
這段代碼演示了把函數(shù)作為對(duì)象使用。如果加一對(duì)圓括號(hào),就是調(diào)用函數(shù),如果不加一對(duì)圓括號(hào),函數(shù)就是對(duì)象,可以賦給另一個(gè)變量,也可以作為函數(shù)參數(shù)值傳入函數(shù)。
由于Python函數(shù)本身是對(duì)象,所以可以在任何地方定義,包括函數(shù)內(nèi)容,這就是Python內(nèi)建函數(shù),代碼如下:
- def talk():
- # 內(nèi)嵌函數(shù)
- def whisper(word="YES"):
- return word.lower()+"..."
- # 調(diào)用內(nèi)嵌函數(shù)
- print(whisper())
- # 調(diào)用talk,whisper函數(shù)在talk內(nèi)部被調(diào)用
- # 輸出:yes...
- talk()
- try:
- # 但whisper函數(shù)在talk函數(shù)外部并不可見(jiàn),所以調(diào)用會(huì)哦拋出異常
- print(whisper())
- except NameError as e:
- print(e)
現(xiàn)在來(lái)總結(jié)下,Python函數(shù)的特性如下:
(1)可以將函數(shù)本身賦給一個(gè)變量,或作為參數(shù)值傳入函數(shù)(方法);
(2)可以在一個(gè)函數(shù)(方法)內(nèi)部定義;
有了這兩個(gè)特性,就意味著函數(shù)可以被另一個(gè)函數(shù)返回,看下面的代碼:
- def getTalk(kind="shout"):
- # 定義第1個(gè)內(nèi)嵌函數(shù)
- def shout(word="yes"):
- return word.capitalize()+"!"
- # 定義第2個(gè)內(nèi)嵌函數(shù)
- def whisper(word="yes") :
- return word.lower()+"..."
- # 根據(jù)參數(shù)值返回特定的函數(shù)
- if kind == "shout":
- # 這里沒(méi)有使用一對(duì)圓括號(hào),所以不是調(diào)用函數(shù),而是返回函數(shù)本身
- return shout
- else:
- return whisper
- # talk是函數(shù)本身,并沒(méi)有被調(diào)用
- talk = getTalk()
- # 輸出函數(shù)本身
- # 輸出:
.shout at 0x7f93a00475e0> - print(talk)
- # 調(diào)用talk函數(shù)(其實(shí)是shout函數(shù))
- print(talk())
- #outputs : Yes!
- # 調(diào)用whisper函數(shù)
- print(getTalk("whisper")())
在這段代碼中,getTalk函數(shù)根據(jù)kind參數(shù)的值返回不同的內(nèi)嵌函數(shù),所以getTalk函數(shù)的返回值是函數(shù)本身,或稱為函數(shù)對(duì)象,如果要調(diào)用函數(shù),需要使用一對(duì)圓括號(hào),如getTalk()()。
根據(jù)這一特性,我們還可以做更多事,例如,在調(diào)用一個(gè)函數(shù)之前自動(dòng)完成其他工作,看下面的代碼:
- def doSomethingBefore(func):
- print("I do something before then I call the function you gave me")
- print(func())
- doSomethingBefore(talk)
其實(shí)這段代碼用doSomethingBefore函數(shù)包裝了talk,這樣可以通過(guò)doSomethingBefore函數(shù)調(diào)用talk函數(shù),并在調(diào)用talk函數(shù)之前輸出一行文本。
4. Python裝飾器的原理
理解了Python函數(shù),再理解Python裝飾器就容易得多了。廢話少說(shuō),先看下面的代碼:
- # 裝飾器函數(shù),參數(shù)是另一個(gè)函數(shù)(被裝飾的函數(shù))
- def my_shiny_new_decorator(a_function_to_decorate):
- # 裝飾器的內(nèi)嵌函數(shù),用來(lái)包裝被修飾的函數(shù)
- def the_wrapper_around_the_original_function():
- # 在調(diào)用被修飾函數(shù)之前輸出一行文本
- print("Before the function runs")
- # 調(diào)用被裝飾函數(shù)
- a_function_to_decorate()
- # 在調(diào)用被修飾函數(shù)之后輸出一行文本
- print("After the function runs")
- # 返回包裝函數(shù)
- return the_wrapper_around_the_original_function
- # 這個(gè)函數(shù)將被my_shiny_new_decorator函數(shù)修飾
- def a_stand_alone_function():
- print("I am a stand alone function, don't you dare modify me")
- # 調(diào)用函數(shù)
- a_stand_alone_function()
- # 修飾a_stand_alone_function函數(shù)
- a_stand_alone_function_decorated = my_shiny_new_decorator(a_stand_alone_function)
- a_stand_alone_function_decorated()
執(zhí)行這段代碼,會(huì)輸出如下內(nèi)容:
- I am a stand alone function, don't you dare modify me
- Before the function runs
- I am a stand alone function, don't you dare modify me
- After the function runs
在這段代碼中,通過(guò)my_shiny_new_decorator函數(shù)修飾了a_stand_alone_function函數(shù),并在調(diào)用a_stand_alone_function函數(shù)前后各輸出了一行文本。其實(shí)這就是Python裝飾器的作用:包裝函數(shù)。只是這里并沒(méi)有使用裝飾器的語(yǔ)法,而是用了最樸素的方式直接調(diào)用了裝飾器函數(shù)來(lái)修飾a_stand_alone_function函數(shù)。
如果用裝飾器來(lái)修飾a_stand_alone_function函數(shù),那么可以用下面的代碼。
- @my_shiny_new_decorator
- def a_stand_alone_function():
- print("I am a stand alone function, don't you dare modify me")
這時(shí)再調(diào)用a_stand_alone_function函數(shù),就會(huì)自動(dòng)使用my_shiny_new_decorator函數(shù)對(duì)a_stand_alone_function函數(shù)進(jìn)行包裝,也就是說(shuō),@my_shiny_new_decorator是my_shiny_new_decorator(a_stand_alone_function)的簡(jiǎn)寫(xiě)形式。
本文轉(zhuǎn)載自微信公眾號(hào)「極客起源」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系極客起源公眾號(hào)。
分享文章:Python裝飾器(Decorator)不過(guò)如此,是我想多了
URL鏈接:http://fisionsoft.com.cn/article/djejcso.html


咨詢
建站咨詢
