簡介
裝飾器是可調用的對象,其參數是另一個函數(被裝飾的函數)。 裝飾器可能會處理被裝飾的函數,然后把它返回,或者將其替換成另一個函數或可調用對象。
形式
假如有個名為 decorate 的裝飾器:
@decorate def target(): print('running target()')
上述代碼的效果與下述寫法一樣:
def target(): print('running target()') target = decorate(target)
兩種寫法的最終結果一樣:上述兩個代碼片段執行完畢后得到的target 不一定是原來那個 target 函數,而是 decorate(target) 返回的函數。
嚴格來說,裝飾器只是語法糖。如前所示,裝飾器可以像常規的可調用對象那樣調用,其參數是另一個函數。有時,這樣做更方便,尤其是做元編程(在運行時改變程序的行為)時。
綜上,裝飾器的一大特性是,能把被裝飾的函數替換成其他函數。第二個特性是,裝飾器在加載模塊時立即執行。
執行裝飾器
裝飾器的一個關鍵特性是,它們在被裝飾的函數定義之后立即運行。這通常是在導入時(即 Python 加載模塊時),如示例 7-2 中的registration.py 模塊所示。
示例 7-2 registration.py 模塊
registry = [] ➊ def register(func): ➋ print('running register(%s)' % func) ➌ registry.append(func) ➍ return func ➎ @register ➏ def f1(): print('running f1()') @register def f2(): print('running f2()') def f3(): ➐ print('running f3()') def main(): ➑ print('running main()') print('registry ->', registry) f1() f2() f3() if __name__=='__main__': main() ➒
❶ registry 保存被 @register 裝飾的函數引用。
❷ register 的參數是一個函數。
❸ 為了演示,顯示被裝飾的函數。
❹ 把 func 存入 registry。
❺ 返回 func:必須返回函數;這里返回的函數與通過參數傳入的一樣。
❻ f1 和 f2 被 @register 裝飾。
❼ f3 沒有裝飾。
❽ main 顯示 registry,然后調用 f1()、f2() 和 f3()。
❾ 只有把 registration.py 當作腳本運行時才調用 main()。
把 registration.py 當作腳本運行得到的輸出如下:
$ python3 registration.py running register(<function f1 at 0x100631bf8>) running register(<function f2 at 0x100631c80>) running main() registry -> [<function f1 at 0x100631bf8>, <function f2 at 0x100631c80>] running f1() running f2() running f3()
注意,register 在模塊中其他函數之前運行(兩次)。調用register 時,傳給它的參數是被裝飾的函數,例如 <function f1 at 0x100631bf8>。
加載模塊后,registry 中有兩個被裝飾函數的引用:f1 和 f2。這兩個函數,以及 f3,只在 main 明確調用它們時才執行。
如果導入 registration.py 模塊(不作為腳本運行),輸出如下:
>>> import registration running register(<function f1 at 0x10063b1e0>) running register(<function f2 at 0x10063b268>)
此時查看 registry 的值,得到的輸出如下:
>>> registration.registry
[<function f1 at 0x10063b1e0>, <function f2 at 0x10063b268>]
示例 7-2 主要想強調,函數裝飾器在導入模塊時立即執行,而被裝飾的函數只在明確調用時運行。這突出了 Python 程序員所說的導入時和運行時之間的區別。
考慮到裝飾器在真實代碼中的常用方式,示例 7-2 有兩個不尋常的地方。
裝飾器函數與被裝飾的函數在同一個模塊中定義。實際情況是,裝飾器通常在一個模塊中定義,然后應用到其他模塊中的函數上。
register 裝飾器返回的函數與通過參數傳入的相同。實際上,大多數裝飾器會在內部定義一個函數,然后將其返回。
雖然示例 7-2 中的 register 裝飾器原封不動地返回被裝飾的函數,但是這種技術並非沒有用處。很多 Python Web 框架使用這樣的裝飾器把函數添加到某種中央注冊處,例如把 URL 模式映射到生成 HTTP 響應的函數上的注冊處。這種注冊裝飾器可能會也可能不會修改被裝飾的函數。