python3 工具模塊functools


簡單的介紹一下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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM