06.23 Python 模塊 Functools

Python 模塊 Functools

模塊 functools 是處理函數的工具庫。不僅可以適配(adapting)和擴展(extending)可調用的對象(callable object),還可以重寫(rewriting)他們。

partial


模塊 functools 的一個很重要的函數就是 partial(),它可以包裝(wrap)一個可調用的對象,並傳遞一些默認的參數。返回的結果仍然是可調用的對象,你可以認為它和原來的函數一樣,只是傳遞了一些默認的參數。調用時可以傳遞參數或者命名參數

Python 模塊 Functools

執行:

Python 模塊 Functools

本例中,使用 partial() 函數包裝了函數 f1,生成了2個新的可調用的函數 p1()和 p2()。p1() 提供了默認值 b=3,p2() 提供了默認值 a=3。partial() 函數也叫偏函數

調用返回的函數時,還也可以通過命名參數覆蓋默認值。需要注意的是,參數如果沒有默認值,調用的時候必須要傳入參數值。

例如,p1() 和 被包裝的函數 f1(),a 都沒有默認值, 如果不傳參數,直接調用 p1(),則拋出異常。

Python 模塊 Functools

其他可調用的對象


partial() 不僅能用在函數上,也可用於其他可調用的對象。例如實現了 __call__() 方法的自定義類

Python 模塊 Functools

執行:

Python 模塊 Functools

本例中,類 A 實現了 __call__() 方法,所以它的實例是可調用的,用 partial() 函數包裝了實例對象 a,然後提供了2個默認值。返回的對象 p 也是可調用的。

方法和函數(Methods and Functions)


partial() 包裝返回的對象是直接可調用的,partialmethod() 返回的也是可調用的,可以作為另一個對象的未綁定的方法(unbound method)。

Python 模塊 Functools

執行:

Python 模塊 Functools

這個例子中,首先定義了一個獨立(standalone)的函數 f1,接收三個參數。在類 A中,分別使用 partialmethod 和 partial 包裝獨立函數 f1。

從輸出可以看到,實例對象可以直接調用使用 partialmethod 生成的方法 m1 ,實例對象自動傳入 f1 的第一個參數 self。

方法 m2 不是一個綁定方法(bound method),所以不能直接調用。self 必須顯示的傳入,否則觸發錯誤 TypeError。調用的時候傳入第一個參數 a.m2(a) 就不會報錯。

獲取裝飾器內函數的屬性


更新裝飾器返回的函數屬性是很有用的,因為返回的函數沒有原始函數的信息(名稱和文檔字符串)。這樣更方便調試程序,知道實際調用的是哪個函數。

Python 模塊 Functools

執行:

Python 模塊 Functools

例子中,定義了函數 show_details() 打印傳入函數的信息,函數名稱、屬性 __name__ 和 __doc__。

當傳入一個未包裝的函數 f2 時,正確打印出了 f2 的信息,但是當打印一個包裝函數 wrap_func 時,返回的信息是 f1 內部函數 h 的信息。這樣當調試的時候,不知道訪問的真實函數是什麼。

現在取消內部函數 h 上的裝飾器 @functools.wraps(f) 註釋,然後再打印輸出:

Python 模塊 Functools

@functools.wraps 可以把原始函數的信息(名稱和文檔字符串)更新到返回的函數上面。

update_wrapper


partial 對象沒有原始函數的 __name__ 和 __doc__ 信息,調用 partial 對象或者調試的時候,會不知道調用的真實函數是誰,使用 update_wrapper() 函數可以將原始函數的信息複製或添加到 partial 對象上。

Python 模塊 Functools

執行:

Python 模塊 Functools

使用 functools.update_wrapper(p1, f1) 把 f1 的 __name__ 和 __doc__都複製到了 partial 對象 p1 上面。

比較


在 Python 2中,類會定義 __cmp__ 方法返回 -1, 0, 1 來確定對象是否小於,等於,大於另一個對象。Python 2.1 中引入了6個比較方法(小於 __lt__、 小於等於 __le__、 等於 __eq__、 不等於__ne__、 大於__gt__、 大於等於__ge__)返回 Boolean 類型確定結果。

Python 3 廢棄(deprecated)了 __cmp__ 方法。

functools 模塊提供了 total_ordering() 方法,實現對象的比較時,你可以不必都實現上述的6個方法,只需要提供少數幾個方法,剩餘的會自動實現。

Python 模塊 Functools

執行:

Python 模塊 Functools

類 A 只提供了 __eq__ 和 __gt__ 兩個比較方法,剩餘的方法由裝飾器自動實現。查看輸出,兩個對象的5次比較都返回了正確的值。

cmp_to_key


因為舊的比較函數已經在 Python 3 中廢棄了,函數 sort() 就不再支持 cmp 參數。舊的比較函數可以使用 cmp_to_key() 方法把自身轉變為 Python 3 中可以用於排序的 key,它確定元素在序列最終的位置。

Python 模塊 Functools

執行:

Python 模塊 Functools

本例中,把舊的比較函數 cmp_old() ,使用方法 cmp_to_key() 轉為排序時用的 key 參數。最後打印了排好序的對象列表。

cmp_to_key() 函數的返回值是 functools 模塊裡的類的一個實例,它已經實現了 Python 3 中新的比較接口(上述的6個比較方法)。

緩存 lru_cache


使用 lru_cache() 裝飾器裝飾一個函數,這個函數就可以把對應的參數和結果作為映射緩存起來。算法使用的是最近最少使用緩存(least-recently-cache)。參數必須是可哈希的(hashable),因為它會用來做為映射的鍵。在後面的調用中,如果參數在之前使用過,就從緩存獲取。

裝飾器添加了獲取緩存信息(cache_info)和清空緩存(cache_clear)的函數。

Python 模塊 Functools

執行:

Python 模塊 Functools

本例中,當運行第一個嵌套循環的時候,進行了4次乘法,查看緩存的信息,命中緩存數 hits 是0,未命中緩存 misses 就是4次,緩存當前大小 currsize 為4。

第二個嵌套循環,應該是進行9次乘法。查看輸出的信息,只有5個 work 輸出,有4次命中 hits 了緩存,5次沒有命中。加上第一個嵌套循環的4次,一共9次 misses。

可以給 lru_cache() 函數一個參數 maxsize ,最大緩存數量。可以防止長時間運行的程序佔用過多的空間。

因為函數的參數必須是可哈希的(hashable),如果傳入的類型是可變的,例如列表,字典就會拋出異常TypeError 類型錯誤。

Python 模塊 Functools

執行:

Python 模塊 Functools

reduce


reduce() 函數接收一個可調用的(callable)對象和一個序列,然後生成單個的值返回。序列的值依次傳入可調用的對象 callable ,每次調用的結果累積起來最後返回。

Python 模塊 Functools

執行:

Python 模塊 Functools

本例中,計算1到6數字之和,最後返回了正確結果為21。6個數字調用了函數 worker 5次。查看輸出可推測出計算過程。首先前兩個數1和2調用函數,得出結果3,3再和第三個數字3相加得出6,以此類推,返回6個數字之和。

reduce() 還有可選的(optional)的第三個參數,初始值 initiallizer,若提供了此值,它也會參加計算。

Python 模塊 Functools

執行:

Python 模塊 Functools

如果序列中值包含一個元素,沒有初始值 initializer 時,就返回那個元素。有初始值,則初始值和那一個元素應用調用對象 callable,然後返回結果。

如果為空序列,又沒有提供默認值,則拋出異常 TypeError 類型錯誤。有默認值則返回默認值。

Python 模塊 Functools

singledispatch


使用 singledispatch 裝飾器裝飾一個函數,會根據參數類型判斷調用哪個方法。

singledispatch 相當於註冊了一個泛型函數(generic functions)。

Python 模塊 Functools

執行:

Python 模塊 Functools


分享到:


相關文章: