標籤:

Python - lru_cache和singledispatch裝飾器

介紹一下 Python3 的兩個裝飾器 lru_cache 和 singledispatch,網上沒什麼的中文文章講這個東西,最近剛好看書學習到的,就來跟大家分享分享

詳情請參看官方文檔:docs.python.org/3/libra

@functools.lru_cache

@functools.lru_cache(maxsize=128, typed=False)

New in version 3.2.

Changed in version 3.3: Added the typed option.

這個裝飾器實現了備忘的功能,是一項優化技術,把耗時的函數的結果保存起來,避免傳入相同的參數時重複計算。lru 是(least recently used)的縮寫,即最近最少使用原則。表明緩存不會無限制增長,一段時間不用的緩存條目會被扔掉。

這個裝飾器支持傳入參數,還能有這種操作的?maxsize 是保存最近多少個調用的結果,最好設置為 2 的倍數,默認為 128。如果設置為 None 的話就相當於是 maxsize 為正無窮了。還有一個參數是 type,如果 type 設置為 true,即把不同參數類型得到的結果分開保存,如 f(3) 和 f(3.0) 會被區分開。

寫了個函數追蹤結果

def track(func):n @functools.wraps(func)n def inner(*args):n result = func(*args)n print("{} --> ({}) --> {} ".format(func.__name__, args[0], result))n return resultn return innern

遞歸函數適合使用這個裝飾器,那就拿經典的斐波那契數列來測試吧

不使用緩存

@trackndef fib(n):n if n < 2:n return nn return fib(n - 2) + fib(n - 1)n

使用緩存

@functools.lru_cache()n@trackndef fib_with_cache(n):n if n < 2:n return nn return fib_with_cache(n - 2) + fib_with_cache(n - 1)n

測試代碼,本電腦的 cpu 是 i5-5200U

fib(10)nn 759 function calls (407 primitive calls) in 0.007 secondsnn Ordered by: cumulative timenn ncalls tottime percall cumtime percall filename:lineno(function)n 1 0.000 0.000 0.007 0.007 {built-in method builtins.exec}n 1 0.000 0.000 0.007 0.007 decorator.py:1(<module>)n 177/1 0.000 0.000 0.007 0.007 decorator.py:5(inner)n 177/1 0.000 0.000 0.007 0.007 decorator.py:12(fib)n 177 0.006 0.000 0.006 0.000 {built-in method builtins.print}n 177 0.000 0.000 0.000 0.000 {method format of str objects}n 2 0.000 0.000 0.000 0.000 decorator.py:4(track)n 3 0.000 0.000 0.000 0.000 functools.py:43(update_wrapper)n 1 0.000 0.000 0.000 0.000 functools.py:422(decorating_function)n 21 0.000 0.000 0.000 0.000 {built-in method builtins.getattr}n 15 0.000 0.000 0.000 0.000 {built-in method builtins.setattr}n 2 0.000 0.000 0.000 0.000 functools.py:73(wraps)n 3 0.000 0.000 0.000 0.000 {method update of dict objects}n 1 0.000 0.000 0.000 0.000 functools.py:391(lru_cache)n 1 0.000 0.000 0.000 0.000 {method disable of _lsprof.Profiler objects}nn# 追蹤結果nfib --> (0) --> 0 nfib --> (1) --> 1 nfib --> (2) --> 1 nfib --> (1) --> 1 nfib --> (0) --> 0 nfib --> (1) --> 1 nfib --> (2) --> 1 nfib --> (3) --> 2 nfib --> (4) --> 3 nfib --> (1) --> 1 nfib --> (0) --> 0 nfib --> (1) --> 1 nfib --> (2) --> 1 nfib --> (3) --> 2 nfib --> (0) --> 0 nfib --> (1) --> 1 nfib --> (2) --> 1 nfib --> (1) --> 1 nfib --> (0) --> 0 nfib --> (1) --> 1 nfib --> (2) --> 1 nfib --> (3) --> 2 nfib --> (4) --> 3 nfib --> (5) --> 5 nfib --> (6) --> 8 nfib --> (1) --> 1 nfib --> (0) --> 0 nfib --> (1) --> 1 nfib --> (2) --> 1 n往後的省略...n

fib(10) 調用了 177 次, 共花費了 0.007

fib_with_cache(10)nn 95 function calls (75 primitive calls) in 0.002 secondsnn Ordered by: cumulative timenn ncalls tottime percall cumtime percall filename:lineno(function)n 1 0.000 0.000 0.002 0.002 {built-in method builtins.exec}n 1 0.000 0.000 0.002 0.002 decorator.py:1(<module>)n 11/1 0.000 0.000 0.002 0.002 decorator.py:5(inner)n 11/1 0.000 0.000 0.002 0.002 decorator.py:18(fib_with_cache)n 11 0.002 0.000 0.002 0.000 {built-in method builtins.print}n 2 0.000 0.000 0.000 0.000 decorator.py:4(track)n 3 0.000 0.000 0.000 0.000 functools.py:43(update_wrapper)n 11 0.000 0.000 0.000 0.000 {method format of str objects}n 1 0.000 0.000 0.000 0.000 functools.py:422(decorating_function)n 21 0.000 0.000 0.000 0.000 {built-in method builtins.getattr}n 15 0.000 0.000 0.000 0.000 {built-in method builtins.setattr}n 2 0.000 0.000 0.000 0.000 functools.py:73(wraps)n 3 0.000 0.000 0.000 0.000 {method update of dict objects}n 1 0.000 0.000 0.000 0.000 functools.py:391(lru_cache)n 1 0.000 0.000 0.000 0.000 {method disable of _lsprof.Profiler objects}nn# 追蹤結果nfib_with_cache --> (0) --> 0 nfib_with_cache --> (1) --> 1 nfib_with_cache --> (2) --> 1 nfib_with_cache --> (3) --> 2 nfib_with_cache --> (4) --> 3 nfib_with_cache --> (5) --> 5 nfib_with_cache --> (6) --> 8 nfib_with_cache --> (7) --> 13 nfib_with_cache --> (8) --> 21 nfib_with_cache --> (9) --> 34 nfib_with_cache --> (10) --> 55 n

可以很明顯的看到,使用緩存的時候,只調用了 11 次就得出了結果,並且花費時間只為 0.002

我們再把數字調大,傳入的參數改為 31

fib(31) nn17426519 function calls (8713287 primitive calls) in 168.122 secondsnn Ordered by: cumulative timenn ncalls tottime percall cumtime percall filename:lineno(function)n 1 0.000 0.000 168.122 168.122 {built-in method builtins.exec}n 1 0.000 0.000 168.122 168.122 decorator.py:1(<module>)n4356617/1 8.046 0.000 168.122 168.122 decorator.py:5(inner)n4356617/1 4.250 0.000 168.122 168.122 decorator.py:12(fib)n 4356617 150.176 0.000 150.176 0.000 {built-in method builtins.print}n 4356617 5.650 0.000 5.650 0.000 {method format of str objects}n 2 0.000 0.000 0.000 0.000 decorator.py:4(track)n 3 0.000 0.000 0.000 0.000 functools.py:43(update_wrapper)n 1 0.000 0.000 0.000 0.000 functools.py:422(decorating_function)n 21 0.000 0.000 0.000 0.000 {built-in method builtins.getattr}n 15 0.000 0.000 0.000 0.000 {built-in method builtins.setattr}n 2 0.000 0.000 0.000 0.000 functools.py:73(wraps)n 3 0.000 0.000 0.000 0.000 {method update of dict objects}n 1 0.000 0.000 0.000 0.000 functools.py:391(lru_cache)n 1 0.000 0.000 0.000 0.000 {method disable of _lsprof.Profiler objects}n

調用了 4356617 次,花費 168.122

fib_with_cache(31) n179 function calls (117 primitive calls) in 0.003 secondsnn Ordered by: cumulative timenn ncalls tottime percall cumtime percall filename:lineno(function)n 1 0.000 0.000 0.003 0.003 {built-in method builtins.exec}n 1 0.000 0.000 0.003 0.003 decorator.py:1(<module>)n 32/1 0.000 0.000 0.003 0.003 decorator.py:5(inner)n 32/1 0.000 0.000 0.003 0.003 decorator.py:18(fib_with_cache)n 32 0.002 0.000 0.002 0.000 {built-in method builtins.print}n 32 0.000 0.000 0.000 0.000 {method format of str objects}n 2 0.000 0.000 0.000 0.000 decorator.py:4(track)n 3 0.000 0.000 0.000 0.000 functools.py:43(update_wrapper)n 1 0.000 0.000 0.000 0.000 functools.py:422(decorating_function)n 21 0.000 0.000 0.000 0.000 {built-in method builtins.getattr}n 15 0.000 0.000 0.000 0.000 {built-in method builtins.setattr}n 2 0.000 0.000 0.000 0.000 functools.py:73(wraps)n 3 0.000 0.000 0.000 0.000 {method update of dict objects}n 1 0.000 0.000 0.000 0.000 functools.py:391(lru_cache)n 1 0.000 0.000 0.000 0.000 {method disable of _lsprof.Profiler objects}n

僅僅調用了 32 次,花費 0.003 秒...,這...,差別太大了,容我算一算花費時間, 56040 倍!

再往後的我就不加了,我怕明天沒有緩存的那個還沒算完

這個裝飾器還提供 cache_clear() 用於清理緩存,以及 cache_info() 用於查看緩存信息

官方還提供了另外一個例子,用於緩存靜態網頁的內容

@lru_cache(maxsize=32)ndef get_pep(num):n Retrieve text of a Python Enhancement Proposaln resource = http://www.python.org/dev/peps/pep-%04d/ % numn try:n with urllib.request.urlopen(resource) as s:n return s.read()n except urllib.error.HTTPError:n return Not Foundn

@singledispatch

New in version 3.4.

使用過別的面向對象語言,如 java 等,肯定熟悉各種方法的重載,但是對於 Python 來說是不支持方法的重載的,不過其為我們提供了一個裝飾器,能將普通函數變為泛函數(generic function)

比如你要針對不同類型的數據進行不同的處理,而又不想將它們寫到一起,那就可以使用 @singledispatch 裝飾器了

@functools.singledispatchndef typecheck():n passnn@typecheck.register(str)ndef _(text):n print(type(text))n print("str--")nn@typecheck.register(list)ndef _(text):n print(type(text))n print("list--")nn@typecheck.register(int)ndef _(text):n print(type(text))n print("int--")n

專門處理的函數無關緊要,所以使用 _ 來表示,當然你也可以寫其他你喜歡的

測試

a = 1ntypecheck(a)n>>> n<class int>nint-- nna = "check"ntypecheck(a)n>>>n<class str>nstr--n

singledispatch 機制一個顯著特徵是,可以在系統的任何地方和任何模塊中註冊專門的函數,如果後來模塊中增加了新的類型,可以輕鬆地添加一個新的專門函數來處理新類型。很像設計模式中的策略模式


推薦閱讀:

TAG:Python |