摘要:實現一個簡單的裝飾器輸出被裝飾函數的運行時間簡單運用運行結果運行過程中,首先輸出裝飾器函數中的內容被裝飾函數運行時間長度函數名稱和實際參數計算結果然后得到最終的計算結果。
函數裝飾器
函數裝飾器用于在源碼中“標記”函數, 以某種方式增強函數的行為,這是一個強大的功能。
函數裝飾器是一個可調用對象,其參數是另外一個函數,即被裝飾函數。裝飾器可能處理被裝飾函數,然后將其返回,或者將其替換成另一個函數或可調用對象。
函數裝飾器的一個重要特性就是,它們在被裝飾的函數定義之后立即運行。
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() running register( ) running main() registry -> [ , ] running f1() running f2() running f3()
可以看到,被裝飾的 f1() 和 f2() 首先被運行,隨后才運行main()中的語句。
被裝飾函數運行時,其本身的內容(示例中print語句)并沒有被執行,而是運行了裝飾器函數中的print語句;這就是裝飾器的作用,替代被裝飾函數,同時裝飾器也可以調用外界自由變量(registry),從而引出一個重要概念:
閉包實例中registry變量和register函數組合的共同體,被成稱為閉包。
該例中有兩個不太尋常的地方:
裝飾器函數和被裝飾函數定義在一個模塊之中;一般來說,兩者應該定義在不同的模塊;
register裝飾器返回的函數與通過參數傳入的相同;實際上,大部分裝飾器會在內部定義一個函數,然后將其返回。
實現一個簡單的裝飾器# clockdeco.py 輸出被裝飾函數的運行時間 import time def clock(func): def clocked(*args): t0 = time.perf_counter() result = func(*args) elapsed = time.perf_counter() - t0 name = func.__name__ arg_str = ",".join(repr(arg) for arg in args) print("[%0.8fs] %s(%s) -> %r" % (elapsed, name, arg_str, result)) return result return clocked
簡單運用:
# clockdeco_demo.py import time from clockdeco import clock @clock def snooze(seconds): time.sleep(seconds) @clock def factorial(n): return 1 if n < 2 else n*factorial(n-1) def main(): print("*" * 40, "Calling snooze(.123)") snooze(.123) print("*" * 40, "Calling factorial(6)") print("6! =", factorial(6)) if __name__ == "__main__": main()
運行結果:
**************************************** Calling snooze(.123) [0.12240868s] snooze(0.123) -> None **************************************** Calling factorial(6) [0.00000068s] factorial(1) -> 1 [0.00020317s] factorial(2) -> 2 [0.00039755s] factorial(3) -> 6 [0.00053638s] factorial(4) -> 24 [0.00062375s] factorial(5) -> 120 [0.00067319s] factorial(6) -> 720 6! = 720
運行過程中,首先輸出裝飾器函數中的內容:
被裝飾函數運行時間長度;
函數名稱和實際參數
計算結果
然后得到最終的計算結果。可見,裝飾器函數的優先級較高
當然,該實例中的裝飾器具有幾個缺點:
不支持關鍵字參數
遮蓋了被裝飾函數的__name__和__doc__屬性
下面的例子對其做出改進:
# clockdeco2.py import time import functools def clock(func): @functools.wraps(func) def clocked(*args, **kwargs): t0 = time.time() result = func(*args, **kwargs) elapsed = time.time() - t0 name = func.__name__ arg_lst = [] if args: arg_lst.append(",".join(repr(arg) for arg in args)) if kwargs: pairs = ["%s=%r" % (k, w) for k, w in sorted(kwargs.items())] arg_lst.append(", ".join(pairs)) arg_str = ", ".join(arg_lst) print("[%0.8fs] %s(%s) -> %r " % (elapsed, name, arg_str, result)) return result return clocked
運用:
# clockdeco_demo.py import time from clockdeco2 import clock @clock def snooze(seconds): time.sleep(seconds) @clock def factorial(n): return 1 if n < 2 else n*factorial(n-1) def main(): print("*" * 40, "Calling snooze(.123)") snooze(.123) print("*" * 40, "Calling factorial(6)") print("6! =", factorial(6)) if __name__ == "__main__": main()
運行結果:
**************************************** Calling snooze(.123) [0.12328553s] snooze(0.123) -> None **************************************** Calling factorial(6) [0.00000000s] factorial(1) -> 1 [0.00000000s] factorial(2) -> 2 [0.00000000s] factorial(3) -> 6 [0.00099683s] factorial(4) -> 24 [0.00099683s] factorial(5) -> 120 [0.00099683s] factorial(6) -> 720 6! = 720
改進后的clockdeco2.py中,使用functools.wraps裝飾器把相關屬性從func復制到clocked中,此外,這個新版本還能正確處理關鍵字參數。functools.wraps是標準庫中的裝飾器,它可以用于裝飾一個函數,但是對于被裝飾函數本身的功能沒有任何影響,它的功能只是傳遞函數內置參數。
參數化clock裝飾器# clockdeco_param.py import time DEFAULT_FMT = "[{elapsed:0.8f}s] {name}({args}) -> {result}" def clock(fmt=DEFAULT_FMT): def decorate(func): def clocked(*_args): t0 = time.time() _result = func(*_args) elapsed = time.time() - t0 name = func.__name__ args = ",".join(repr(arg) for arg in _args) result = repr(_result) print(fmt.format(**locals())) return _result return clocked return decorate
def clock 是參數化裝飾器工廠函數
decorate(func) 是真正的裝飾器
clocked 包裝被裝飾的函數
示例1:
# clockdeco_param_demo.py import time from clockdeco_param import clock @clock() def snooze(seconds): time.sleep(seconds) for i in range(3): snooze(.123)
運行結果:
[0.12367034s] snooze(0.123) -> None [0.12367010s] snooze(0.123) -> None [0.12366986s] snooze(0.123) -> None
示例2:
# clockdeco_param_demo.py import time from clockdeco_param import clock @clock("{name}:{elapsed}s") def snooze(seconds): time.sleep(seconds) for i in range(3): snooze(.123)
運行結果:
snooze:0.12366843223571777s snooze:0.12369871139526367s snooze:0.12366509437561035s
示例3:
# clockdeco_param_demo.py import time from clockdeco_param import clock @clock("{name}({args}) dt={elapsed:0.3f}s") def snooze(seconds): time.sleep(seconds) for i in range(3): snooze(.123)
運行結果:
snooze(0.123) dt=0.124s snooze(0.123) dt=0.124s snooze(0.123) dt=0.124s
分析三個示例可以看出,當裝飾器clock的參數不同時,被裝飾函數運行所得結果也會不同。
python中參數化裝飾器的用意在于將更多的參數傳送給裝飾器,因為裝飾器的第一個參數一定是被裝飾函數。
備忘裝飾器 functools.lru_cachefunctools.lru_cache和functools.wraps一樣,也是一個python內置裝飾器,它的功能是將耗時的函數結果保存起來,避免傳圖相同的參數造成重復計算,從而節省代碼運行時間。
下面以斐波那契數列寫一個案例:
使用functools.lru_cache
import functools
from clockdeco import clock
@functools.lru_cache()
@clock
def fibonacci(n):
if n < 2: return n return fibonacci(n-2) + fibonacci(n-1)
if __name__=="__main__":
print(fibonacci(30))
運行結果:
[0.00000000s] fibonacci(0) -> 0 [0.00000068s] fibonacci(1) -> 1 ...... [0.00000271s] fibonacci(29) -> 514229 [0.00542815s] fibonacci(30) -> 832040 832040
多次運行,計算fibonacci(30)大概耗時0.005秒左右
作為對比:
import functools from clockdeco import clock @clock def fibonacci(n): if n < 2: return n return fibonacci(n-2) + fibonacci(n-1) if __name__=="__main__": print(fibonacci(30))
運行結果:
....... [156.42139917s] fibonacci(28) -> 317811 [230.80184171s] fibonacci(29) -> 514229 [368.52227404s] fibonacci(30) -> 832040 832040
嗯……陷入沉思,雖然筆記本渣渣配置,但是運行了6分鐘,差距太大
總結函數裝飾器就是用來裝飾函數的函數,并使用內置函數替代被裝飾函數
被裝飾函數在定義時就運行,無需顯示調用
內置裝飾器functools.wraps可以為被裝飾函數賦值內置屬性
參數化裝飾器可以接受不同參數并適用于被裝飾函數,顯示不同的作用,相當于裝飾器中的定制化
參數化的裝飾器嵌套層次較深,邏輯相對復雜
內置裝飾器functools.lru_cache采用緩存機制,存儲需要多次調用的函數值,從而降低代碼運行時間
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/42259.html
摘要:前言最近跟著流暢的和學習,看到裝飾器部分,有些頭大倒不是因為概念難以理解,而是書和網上文章中有些地方有些矛盾之處在簡單學習和實踐之后,整理出我對裝飾器的理解如下裝飾器的定義在不同語境下,裝飾器有不一樣的含義,我大致認為有種定義一種把另一個對 前言 最近跟著《流暢的Python》和《Python Cookbook》學習,看到裝飾器部分,有些頭大倒不是因為概念難以理解,而是書和網上文章中有...
摘要:裝飾器介紹中的裝飾器的目的是為一個目標函數添加額外的功能卻不修改函數本身。裝飾器的本身其實是一個特殊的函數。那么有啥更好的解決方式呢裝飾器代碼像上面這么寫,可以較好地解決了上面提到的第一個問題。裝飾器語法糖放在函數前面,相當于執行了等。 怎么理解python中的裝飾器 一個比喻 知乎上有一個比較形象的比喻 https://www.zhihu.com/questio...:人類穿著內褲很...
摘要:的裝飾器可以實現在代碼運行期間修改函數的上下文,即可以定義函數在執行之前進行何種操作和函數執行后進行何種操作,而函數本身并沒有任何的改變。中的參數,實際上則是傳遞給實際上是的參數因為裝飾器也是個函數,那么裝飾器自己的能不能有參數傳遞呢。 Python的裝飾器可以實現在代碼運行期間修改函數的上下文, 即可以定義函數在執行之前進行何種操作和函數執行后進行何種操作, 而函數本身并沒有任何的改...
摘要:示例如下靜態路由使用動態變量的路由未指定變量類型使用動態變量的路由指定變量類型指定的路由變量,可以作為被裝飾的函數參數傳入進來。 開始決定認真的在網上寫一些東西,主要原因還是在于希望能提升學習效果。雖說python寫了有幾年,但是web后端框架的確沒怎么接觸過,買了本狗書寥寥草草的過了一遍,發現很多東西還是理解不深,真的是好記性不如爛筆頭,知識也要從基礎開始,退回來好好看看官方文檔,再...
閱讀 3633·2021-11-22 09:34
閱讀 3201·2021-11-15 11:38
閱讀 3083·2021-10-27 14:16
閱讀 1266·2021-10-18 13:35
閱讀 2440·2021-09-30 09:48
閱讀 3441·2021-09-29 09:34
閱讀 1669·2019-08-30 15:54
閱讀 1832·2019-08-26 11:57