裝飾器(decorator)是一種高級Python語法。裝飾器可以對一個函數(shù)、方法或者類進(jìn)行加工。在Python中,我們有多種方法對函數(shù)和類進(jìn)行加工,比如在Python閉包中,我們見到函數(shù)對象作為某一個函數(shù)的返回結(jié)果。相對于其它方式,裝飾器語法簡單,代碼可讀性高。因此,裝飾器在Python項(xiàng)目中有廣泛的應(yīng)用。
前面快速介紹了裝飾器的語法,在這里,我們將深入裝飾器內(nèi)部工作機(jī)制,更詳細(xì)更系統(tǒng)地介紹裝飾器的內(nèi)容,并學(xué)習(xí)自己編寫新的裝飾器的更多高級語法。
什么是裝飾器
裝飾是為函數(shù)和類指定管理代碼的一種方式。Python裝飾器以兩種形式呈現(xiàn):
【1】函數(shù)裝飾器在函數(shù)定義的時(shí)候進(jìn)行名稱重綁定,提供一個邏輯層來管理函數(shù)和方法或隨后對它們的調(diào)用。
【2】類裝飾器在類定義的時(shí)候進(jìn)行名稱重綁定,提供一個邏輯層來管理類,或管理隨后調(diào)用它們所創(chuàng)建的實(shí)例。
簡而言之,裝飾器提供了一種方法,在函數(shù)和類定義語句的末尾插入自動運(yùn)行的代碼——對于函數(shù)裝飾器,在def的末尾;對于類裝飾器,在class的末尾。這樣的代碼可以扮演不同的角色。
裝飾器提供了一些和代碼維護(hù)性和審美相關(guān)的有點(diǎn)。此外,作為結(jié)構(gòu)化工具,裝飾器自然地促進(jìn)了代碼封裝,這減少了冗余性并使得未來變得更容易。
函數(shù)裝飾器
通過在一個函數(shù)的def語句的末尾運(yùn)行另一個函數(shù),把最初的函數(shù)名重新綁定到結(jié)果。
用法
裝飾器在緊挨著定義一個函數(shù)或方法的def語句之前的一行編寫,并且它由@符號以及緊隨其后的對于元函數(shù)的一個引用組成——這是管理另一個函數(shù)的一個函數(shù)(或其他可調(diào)用對象)。
在編碼上,函數(shù)裝飾器自動將如下語法:
1
2
3
4
|
@decorator def F(arg): ... F( 99 ) |
映射為這個對等形式:
1
2
3
4
|
def F(arg): ... F = decorator(F) F( 99 ) |
這里的裝飾器是一個單參數(shù)的可調(diào)用對象,它返回與F具有相同數(shù)目的參數(shù)的一個可調(diào)用對象。
當(dāng)隨后調(diào)用F函數(shù)的時(shí)候,它自動調(diào)用裝飾器所返回的對象。
換句話說,裝飾實(shí)際把如下的第一行映射為第二行(盡管裝飾器只在裝飾的時(shí)候運(yùn)行一次)
1
2
|
fun( 6 , 7 ) decorator(func)( 6 , 7 ) |
這一自動名稱重綁定也解釋了之前介紹的靜態(tài)方法和property裝飾器語法的原因:
1
2
3
4
5
|
class C: @staticmethod def meth(...):... @property def name( self ):... |
實(shí)現(xiàn)
裝飾器自身是返回可調(diào)用對象的可調(diào)用對象。實(shí)際上,它可以是任意類型的可調(diào)用對象,并且返回任意類型的可調(diào)用對象:函數(shù)和類的任何組合都可以使用,盡管一些組合更適合于特定的背景。
有一種常用的編碼模式——裝飾器返回了一個包裝器,包裝器把最初的函數(shù)保持到一個封閉的作用域中:
1
2
3
4
5
6
7
8
9
|
def decorator(F): def wrapper( * args): # 使用 F 和 *args # 調(diào)用原來的F(*args) return wrapper @decorator def func(x,y): ... func( 6 , 7 ) |
當(dāng)隨后調(diào)用名稱func的時(shí)候,它確實(shí)調(diào)用裝飾器所返回的包裝器函數(shù);隨后包裝器函數(shù)可能運(yùn)行最初的func,因?yàn)樗谝粋€封閉的作用域中仍然可以使用。
為了對類做同樣的事情,我們可以重載調(diào)用操作:
1
2
3
4
5
6
7
8
9
10
|
class decorator: def __init__( self ,func): self .func = func def __call__( self , * args): # 使用self.func和args # self.func(*args)調(diào)用最初的func @decorator def func(x,y): ... func( 6 , 7 ) |
但是,要注意的是,基于類的代碼中,它對于攔截簡單函數(shù)有效,但當(dāng)它應(yīng)用于類方法函數(shù)時(shí),并不很有效:
如下反例:
1
2
3
4
5
6
7
8
9
|
class decorator: def __init__( self ,func): self .func = func def __call__( self , * args): # 調(diào)用self.func(*args)失敗,因?yàn)镃實(shí)例參數(shù)無法傳遞 class C: @decorator def method( self ,x,y): ... |
這時(shí)候裝飾的方法重綁定到一個類的方法上,而不是一個簡單的函數(shù),這一點(diǎn)帶來的問題是,當(dāng)裝飾器的方法__call__隨后運(yùn)行的時(shí)候,其中的self接受裝飾器類實(shí)例,并且類C的實(shí)例不會包含到一個*args中。
這時(shí)候,嵌套函數(shù)的替代方法工作得更好:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
def decorator: def warpper( * args): # ... return wrapper @decorator def func(x,y): ... func( 6 , 7 ) class C: @decorator def method( self ,x,y): ... x = C() x.method( 6 , 7 ) |
類裝飾器
類裝飾器與函數(shù)裝飾器使用相同的語法和非常相似的編碼方式。類裝飾器是管理類的一種方式,或者用管理或擴(kuò)展類所創(chuàng)建的實(shí)例的額外邏輯,來包裝實(shí)例構(gòu)建調(diào)用。
用法
假設(shè)類裝飾器返回一個可調(diào)用對象的一個單參數(shù)的函數(shù),類裝飾器的語法為:
1
2
3
4
|
@decorator class C: ... x = C( 99 ) |
等同于下面的語法:
1
2
3
4
|
class C: ... C = decorator(C) x = C( 99 ) |
直接效果是隨后調(diào)用類名會創(chuàng)建一個實(shí)例,該實(shí)例會觸發(fā)裝飾器所返回的可調(diào)用對象,而不是調(diào)用最初的類自身。
實(shí)現(xiàn)
類裝飾器返回的可調(diào)用對象,通常創(chuàng)建并返回最初的類的一個新的實(shí)例,以某種方式來擴(kuò)展對其接口的管理。例如,下面的實(shí)例插入一個對象來攔截一個類實(shí)例的未定義的屬性:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
def decorator( cls ): class Wrapper: def __init__( self , * args): self .wrapped = cls ( * args) def __getattr__( self ,name): return getattr ( self .wrapped,name) return Wrapper @decorator class C: # C = decorator(C) def __init__( self ,x,y): # Run by Wrapper.__init__ self .attr = 'spam' x = C( 6 , 7 ) # 等價(jià)于Wrapper(6,7) print (x.attr) |
在這個例子中,裝飾器把類的名稱重新綁定到另一個類,這個類在一個封閉的作用域中保持了最初的類。
就像函數(shù)裝飾器一樣,類裝飾器通常可以編寫為一個創(chuàng)建并返回可調(diào)用對象的“工廠”函數(shù)。
裝飾器嵌套
有時(shí)候,一個裝飾器不夠,裝飾器語法允許我們向一個裝飾器的函數(shù)或方法添加包裝器邏輯的多個層。這種形式的裝飾器的語法為:
1
2
3
4
5
|
@A @B @C def f(...): ... |
如下這樣轉(zhuǎn)換:
1
2
3
|
def f(...): ... f = A(B(C(f))) |
這里,最初的函數(shù)通過3個不同的裝飾器傳遞,每個裝飾器處理前一個結(jié)果。
裝飾器參數(shù)
函數(shù)裝飾器和類裝飾器都能接受參數(shù),如下:
1
2
3
4
|
@decorator (A,B) def F(arg): ... F( 99 ) |
自動映射到其對等形式:
1
2
3
4
|
def F(arg): ... F = decorator(A,B)(F) F( 99 ) |
裝飾器參數(shù)在裝飾之前就解析了,并且它們通常用來保持狀態(tài)信息供隨后的調(diào)用使用。例如,這個例子中的裝飾器函數(shù),可能采用如下形式:
1
2
3
4
5
6
7
|
def decorator(A,B): # 保存或使用A和B def actualDecorator(F): # 保存或使用函數(shù) F # 返回一個可調(diào)用對象 return callable return actualDecorator |
以上,這是裝飾器的基礎(chǔ)知識,接下來將學(xué)習(xí)編寫自己的裝飾器。