簡單的介紹一下functools標准模塊方便自己查閱使用。
functools主要具有以下函數
cmp_to_key,將舊式的比較函數轉換關鍵字函數;
@lru_cache, 裝飾器,是一種優化技術,將耗時的操作結果緩存,避免重復操作
partial,偏函數,針對函數起作用,將函數的某幾個參數固定,重新返回一個可調用對象
reduce,計算可迭代對象的累加值;
@total_ordering,類裝飾器,為一個類添加比較的方法。
update_wrapper,更新一個包裹(wrapper)函數,使其看起來更像被包裹(wrapped)的函數,簡而言之就是防止被裝飾的函數的基本屬性被修改。
@wraps,裝飾器,簡化調用update_wrapper的過程;
@singledispatch, 單分派泛函數,類似於c++的重載
具體使用方法:
functools.cmp_to_key(func)
舊式的比較函數是接受兩個參數,返回負值,0和正值比較大小的,該函數將舊式函數轉化為可以被sorted(), min(), max(), heapq.nlargest(), heapq.nsmallest()等函數接受的key。
demo:
import functools
from collections import namedtuple Point = namedtuple('Point', ['x', 'y']) def compare(p1, p2): """根據y坐標比較大小""" if p1.y > p2.y: return 1 elif p1.y == p2.y: return 0 else: return -1 if __name__ == '__main__': lst = [Point(-x, 2 * x - 1) for x in range(5)] res = sorted(lst, key=functools.cmp_to_key(compare)) print(res)
控制台輸出按照y坐標排序的結果
[Point(x=0, y=-1), Point(x=-1, y=1), Point(x=-2, y=3), Point(x=-3, y=5), Point(x=-4, y=7)]
@functools.lru_cache(maxsize=128, typed=False)
這個裝飾器實現了備忘的功能,是一項優化技術,把耗時的函數的結果保存起來,避免傳入相同的參數時重復計算。lru 是(least recently used)的縮寫,即最近最少使用原則。表明緩存不會無限制增長,一段時間不用的緩存條目會被扔掉。
這個裝飾器支持傳入參數,maxsize 是保存最近多少個調用的結果,最好設置為 2 的倍數,默認為 128。如果設置為 None 的話就相當於是 maxsize 為正無窮了。還有一個參數是 type,如果 type 設置為 true,即把不同參數類型得到的結果分開保存,如 f(3) 和 f(3.0) 會被區分開。
為了幫助調優maxsize參數,封裝的函數使用cache_info()函數進行檢測,該函數返回一個命名元組,顯示hits, misses、maxsize和currsize。在多線程環境中,hits和misses是近似的。裝飾器還提供了一個cache_clear()函數,用於清除或使緩存失效。
該裝飾器一般用於需要反復利用以前計算出來的值的函數,如遞歸就是一個很好的例子。下面用官網所給出的斐波那契數列例子,並用CProfile模塊檢測效率。
import functools import cProfile def fib(n): if n < 2: return n return fib(n-1) + fib(n-2) @functools.lru_cache() def lru_fib(n): if n < 2: return n return lru_fib(n-1) + lru_fib(n-2) if __name__ == '__main__': cProfile.run('print(fib(35))', sort='cumtime') cProfile.run('print(lru_fib(35))', sort='cumtime') print(lru_fib.cache_info())
運行以上代碼,控制台輸出
9227465 29860707 function calls (5 primitive calls) in 8.239 seconds Ordered by: cumulative time ncalls tottime percall cumtime percall filename:lineno(function) 1 0.000 0.000 8.239 8.239 {built-in method builtins.exec} 1 0.000 0.000 8.239 8.239 <string>:1(<module>) 29860703/1 8.239 0.000 8.239 8.239 lru_cache.py:14(fib) 1 0.000 0.000 0.000 0.000 {built-in method builtins.print} 1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects} 9227465 40 function calls (5 primitive calls) in 0.000 seconds Ordered by: cumulative time ncalls tottime percall cumtime percall filename:lineno(function) 1 0.000 0.000 0.000 0.000 {built-in method builtins.exec} 1 0.000 0.000 0.000 0.000 <string>:1(<module>) 36/1 0.000 0.000 0.000 0.000 lru_cache.py:20(lru_fib) 1 0.000 0.000 0.000 0.000 {built-in method builtins.print} 1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects} CacheInfo(hits=33, misses=36, maxsize=128, currsize=36)
結果顯示未調用緩存優化的遞歸了2000多萬次,而后者只調用了36次,用的時間也是差了很多,從這就可以看出緩存技術的優勢。
functools.partial(func, *args, **keywords)
偏函數:partial函數的用法就是:將所作用的函數作為partial()函數的第一個參數,原函數的各個參數依次作為partial()函數的后續參數,原函數有關鍵字參數的一定要帶上關鍵字,沒有的話,按原有參數順序進行補充。具體作用就是當函數的參數個數太多,需要簡化時,使用 functools.partial 可以創建一個新的函數,這個新函數可以固定住原函數的部分參數,從而在調用時更簡單。當然,裝飾器也可以實現,如果,我們不嫌麻煩的話。下面是例子。
import functools def parabola(a, b, c, x): """求拋物線的值""" return a*x*x + b*x + c if __name__ == '__main__': print(parabola(1, 1, 1, 5)) # 輸出31 par = functools.partial(parabola, 1, 1, 1) print(par(5)) # 輸出31 print(par(1)) # 輸出3
綜上,所謂偏函數就是凍結原函數的部分參數,讓下次調用可以減少填入的參數,(就是一種偷懶的方法)。
functools.reduce(function, iterable[, initializer])
將擁有兩個參數的函數累積作用於可迭代對象的每一個元素上。例如reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) 依次計算((((1+2)+3)+4)+5),累加的和作為下一次迭代的第一個參數。
In[2]: import functools In[3]: functools.reduce(lambda x, y: x+y, range(5)) Out[3]: 10 In[4]: functools.reduce(lambda x, y: x+y, range(5), 10) Out[4]: 20 In[6]: functools.reduce(lambda x, y: x ^ y, range(5)) # 0^1^2^3^4 Out[6]: 4
@functools.total_ordering
為一個類補充剩余的比較方法,不用自己把所有的比較方法都寫全。但是類本身必須已經實現__eq__方法,並且還實現了 __lt__(), __le__(), __gt__(), or __ge__()的其中一個方法。舉個例子如下所示,如果不加該裝飾器,直接比較s1>=s2會報錯,加了裝飾器后,就已經補全了比較的方法,可以讓自己少寫一些比較方法。
import functools @functools.total_ordering class Student(object): def __init__(self, score): self.score = score def __eq__(self, other): return self.score == other.score def __gt__(self, other): return self.score > other.score if __name__ == '__main__': s1 = Student(60) s2 = Student(70) print(s1 >= s2) # 輸出False
functools.update_wrapper(wrapper, wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)
@functools.wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)
上面兩個方法一起說,下面這個裝飾器是上面這個方法的封裝,利用這個裝飾器可以保證被裝飾函數的基本性質不變。基本性質有以下這些
WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__', '__annotations__')
WRAPPER_UPDATES = ('__dict__',)
import functools def _wrapper(func): def inner(*args, **kwargs): print('---inner---') return func(*args, **kwargs) return inner def _wrapper2(func): @functools.wraps(func) def inner(*args, **kwargs): print('---inner---') return func(*args, **kwargs) return inner @_wrapper def func(): """f2""" print('---func---') @_wrapper2 def func2(): """f2""" print('---func2---') if __name__ == '__main__': print(func.__name__) # 輸出inner print(func.__doc__) # 輸出None print(func2.__name__) # 輸出func2 print(func2.__doc__) # 輸出f2
@functools.singledispatch
利用這個裝飾器可以事先c++里的重載功能,python本身是動態語言,就不支持重載功能,如果要判斷參數的類型,就可以用if...else判斷,但利用這個單分派泛函數,就可以把普通的函數變為泛函數,達到c++等語言里的重載一樣的效果。
import functools @functools.singledispatch def fun(arg): print(type(arg), arg) @fun.register(int) def _(arg): print('整數', type(arg), arg) @fun.register(float) def _(arg): print('浮點數', type(arg), arg) @fun.register(str) def _(arg): print('字符串', type(arg), arg) if __name__ == '__main__': fun(None) # 輸出 <class 'NoneType'> None fun(10) # 輸出 整數 <class 'int'> 10 fun(1.2) # 輸出 浮點數 <class 'float'> 1.2 fun('string') # 輸出 字符串 <class 'str'> string
就以上這些了。
參考
1. https://blog.csdn.net/weixin_40156487/article/details/82460854
2. https://www.cnblogs.com/zhbzz2007/p/6001827.html
3. 官方文檔 https://docs.python.org/3/library/functools.html?highlight=functool#module-functools
4. https://zhuanlan.zhihu.com/p/27643991