裝飾器的一個關鍵特性是,它們在被裝飾的函數定義之后立即運行。這 通常是在導入時(即 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()
運行結果
running register(<function f1 at 0x0000027913AA8708>)
running register(<function f2 at 0x0000027913AA8E58>)
running main()
registry -> [<function f1 at 0x0000027913AA8708>, <function f2 at 0x0000027913AA8E58>]
running f1()
running f2()
running f3()
注意,register 在模塊中其他函數之前運行(兩次)。調用 register 時,傳給它的參數是被裝飾的函數,例如 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 響應的函 數上的注冊處。這種注冊裝飾器可能會也可能不會修改被裝飾的函數。