Python自帶的 functools 模塊提供了一些常用的高階函數,也就是用於處理其它函數的特殊函數。換言之,就是能使用該模塊對可調用對象進行處理。英文文檔
一、functools模塊函數概覽
- functools.cmp_to_key(func)
- functools.total_ordering(cls)
- functools.reduce(function, iterable[, initializer])
- functools.partial(func[, args][, *keywords])
- functools.update_wrapper(wrapper, wrapped[, assigned][, updated])
- functools.wraps(wrapped[, assigned][, updated])
- functools.lru_cache(maxsize=128, typed=False)
- functools.partialmethod(func, *args, **keywords)
- functools.singledispatch(default)
二、functools.cmp_to_key()
語法:
functools.cmp_to_key(func)
該函數用於將舊式的比較函數轉換爲關鍵字函數。
舊式的比較函數:接收兩個參數,返回比較的結果。返回值小於零則前者小於後者,返回值大於零則相反,返回值等於零則兩者相等。
關鍵字函數:接收一個參數,返回其對應的可比較對象。例如 sorted(), min(), max(), heapq.nlargest(), heapq.nsmallest(), itertools.groupby() 都可作爲關鍵字函數。
在 Python 3 中,有很多地方都不再支持舊式的比較函數,此時可以使用 cmp_to_key() 進行轉換。
示例:
1 |
|
三、functools.total_ordering()
版本3.2中新增。
版本3.4中修改:如果是不能識別的類型,現在支持從底層比較函數返回NotImplemented。
語法:
functools.total_ordering(cls)
這是一個類裝飾器,用於自動實現類的比較運算。
只需要在類中實現 __eq__() 方法和以下方法中的任意一個 __lt__(), __le__(), __gt__(), __ge__(),那麼 total_ordering() 就能自動幫我們實現餘下的幾種比較運算。
示例:
1 2 3 4 5 6 7 8 |
|
給定的一個類定義了一個或多個富比較方法,該類裝飾器提供剩下的。這簡化了指定所有富比較操作的工作量。
注意:雖然該裝飾器能很容易的創建行爲良好的完全有序類型,但會導致衍生出的比較函數執行的更慢,以及更復雜的堆棧跟蹤。如果性能基準測試表明這是程序的瓶頸,則實現所有六個富比較函數可能會是提高速度的方式。
四、functools.reduce()
語法:
functools.reduce(function, iterable[, initializer])
該函數與 Python 內置的 reduce() 函數相同,主要用於編寫兼容 Python 3 的代碼。
五、functools.partial()
語法:
functools.partial(func[, *args][, **keywords])
該函數返回一個 partial 對象,調用該對象的效果相當於調用 func 函數,並傳入位置參數 args 和關鍵字參數 keywords 。如果調用該對象時傳入了位置參數,則這些參數會被添加到 args 中。如果傳入了關鍵字參數,則會被添加到 keywords 中。
partial() 函數的等價實現大致如下:
1 2 3 4 5 6 7 8 9 |
|
partial() 函數主要用於“凍結”某個函數的部分參數,返回一個參數更少、使用更簡單的函數對象。
示例:
1 2 3 4 5 |
|
partial()
創建可調用的 partial 對象。它們有三個只讀屬性:
5.1 partial.func
一個可調用的對象或函數。調用partial對象會轉爲使用新的參數和關鍵字參數調用func。
5.2 partial.args
最左邊的位置參數會優先作爲位置參數提供給partial對象調用。
5.3 partial.keywords
partial 對象被調用時提供關鍵字參數。
partial 對象與函數對象類似,它們可以被調用,有弱引用,並且可以有屬性。但有一些重要的區別。對於實例,__name__
和__doc__
屬性不會自動創建。同時,在類中定義的partial對象的行爲類似靜態方法,在實例屬性查找時,不會轉換爲綁定方法。
六、functools.update_wrapper()
語法:
functools.update_wrapper(wrapper, wrapped[, assigned][, updated])
該函數用於更新包裝函數(wrapper),使它看起來像原函數一樣。可選的參數是一個元組,assigned 元組指定要直接使用原函數的值進行替換的屬性,updated 元組指定要對照原函數進行更新的屬性。這兩個參數的默認值分別是模塊級別的常量:WRAPPER_ASSIGNMENTS 和 WRAPPER_UPDATES。前者指定了對包裝函數的 __name__, __module__, __doc__ 屬性進行直接賦值,而後者指定了對包裝函數的 __dict__ 屬性進行更新。
該函數主要用於裝飾器函數的定義中,置於包裝函數之前。如果沒有對包裝函數進行更新,那麼被裝飾後的函數所具有的元信息就會變爲包裝函數的元信息,而不是原函數的元信息。
七、functools.wraps()
語法:
functools.wraps(wrapped[, assigned][, updated])
wraps() 簡化了 update_wrapper() 函數的調用。它等價於 partial(update_wrapper, wrapped=wrapped, assigned, updated=updated)。
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
如果不使用這個函數,示例中的函數名就會變成 wrapper ,並且原函數 example() 的說明文檔(docstring)就會丟失。
八、@functools.lru_cache(maxsize=128, typed=False)
版本3.2中新增。
版本3.3中修改:增加可選參數typed參數。
裝飾器用一個有記憶的調用包裝一個函數,它可以保存最近maxsize次調用。當使用同樣的參數定期調用費時或I/O綁定的函數時,它可以節省時間。
因爲使用字典緩存結果,所以函數的位置和關鍵字參數必須是hashable。
如果maxsize設置爲None,則禁用LRU功能,並且緩存可以無限增長。當maxsize設置爲$ 2^n $時,性能最佳。
如果typed設置爲真,則不同類型的函數參數會分別緩存。例如,f(3)
和f(3.0)
將視爲不同結果的不同調用。
爲了幫助測量緩存的有效性並調整maxsize參數,包裝函數使用cache_info()
函數返回一個命名元組,包括hits,misses,maxsize和currsize。在多線程環境中,hits和misses是近似值。
裝飾器還提供了cache_clear()
函數用於清除緩存,或者讓緩存失效。
原始的底層函數通過wrapped屬性訪問。這對於內省,繞過緩存,或者重新裝飾函數很有用。
當最近調用是即將調用的最佳調用因子時(例如,新聞服務器上的最受歡迎文章常常每天改變),LRU(least recently used)緩存效果最好。緩存的大小限制確保緩存不會在長時間運行的進程(如web服務器)上不受限制的增長。
用於靜態Web內容的LRU緩存示例:
@lru_cache(maxsize=32)
def get_pep(num):
'Retrieve text of a Python Enhancement Proposal'
resource = 'http://www.python.org/dev/peps/pep-%04d/' % num
try:
with urllib.request.urlopen(resource) as s:
return s.read()
except urllib.error.HTTPError:
return 'Not Found'
>>> for n in 8, 290, 308, 320, 8, 218, 320, 279, 289, 320, 9991:
... pep = get_pep(n)
... print(n, len(pep))
>>> get_pep.cache_info()
CacheInfo(hits=3, misses=8, maxsize=32, currsize=8)
使用緩存實現動態編程技術高效計算斐波那契數列的示例:
@lru_cache(maxsize=None)
def fib(n):
if n < 2:
return n
return fib(n-1) + fib(n-2)
>>> [fib(n) for n in range(16)]
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610]
>>> fib.cache_info()
CacheInfo(hits=28, misses=16, maxsize=None, currsize=16)
九、類方法 functools.partialmethod(func, *args, **keywords)
版本3.4中新增。
返回一個行爲類似partial的新partialmethod描述符,除了它是用於方法定義,而不是直接調用。
func必須是一個descriptor或者可調用對象(兩個對象都像常規函數一樣作爲descriptor)。
當func是一個descriptor(比如普遍的Python函數,classmethod()
,staticmethod()
,abstractmethod()
,或者其它partialmethod實例時,get的調用會委託給底層的descriptor,並返回一個適當的partial對象。
當func不是可調用的descriptor時,會動態創建一個適當的綁定方法。用於方法時,該行爲類似普通的Python函數:self參數會插入爲第一個位置參數,甚至在傳遞給partialmethod構造器的args和keywords之前。
示例:
>>> class Cell(object):
... def __init__(self):
... self._alive = False
... @property
... def alive(self):
... return self._alive
... def set_state(self, state):
... self._alive = bool(state)
... set_alive = partialmethod(set_state, True)
... set_dead = partialmethod(set_state, False)
...
>>> c = Cell()
>>> c.alive
False
>>> c.set_alive()
>>> c.alive
True
十、@functools.singledispatch(default)
版本3.4中新增。
將函數轉換爲single-dispatch generic函數。
使用@singledispatch裝飾器定義generic函數。注意,dispatch發生在第一個參數的類型上,相應的創建函數:
>>> from functools import singledispatch
>>> @singledispatch
... def fun(arg, verbose=False):
... if verbose:
... print('Let me juset say,', end=' ')
... print(arg)
使用generic函數的register()
屬性添加函數的重載實現。這是一個裝飾器,接受一個類型參數,並裝飾實現該類型操作的函數:
>>> @fun.register(int)
... def _(arg, verbose=False):
... if verbose:
... print('Strength in numbers, eh?', end=' ')
... print(arg)
...
>>> @fun.register(list)
... def _(arg, verbose=False):
... if verbose:
... print('Enumerate this:')
... for i, elem in enumerate(arg):
... print(i, elem)
爲了能夠註冊lambda表達式和預先存在的函數,register()
可以用於函數形式:
>>> def nothing(arg, verbose=False):
... print('Nothing.')
...
>>> fun.register(type(None), nothind)
register()
屬性返回未裝飾的函數,可以使用裝飾堆疊,pickling,以及爲每個變體單獨創建單元測試:
>>> @fun.register(float)
... @fun.register(Decimal)
... def fun_num(arg, verbose=False):
... if verbose:
... print('Half of your number:', end=' ')
... print(arg / 2)
...
>>> fun_num is fun
False
調用時,generic函數根據第一個參數的類型dispatch:
>>> fun('Hello World.')
Hello World.
>>> fun('test.', verbose=True)
Let me just say, test.
>>> fun(42, verbose=True)
Strength in numbers, eh? 42
>>> fun(['spam', 'spam', 'eggs', 'spam'], verbose=True)
Enumerate this:
0 spam
1 spam
2 eggs
3 spam
>>> fun(None)
Nothing.
>>> fun(1.23)
0.615
當沒有註冊特定類型的實現時,其方法解析順序用於查找更通用的實現。用@singledispatch
裝飾的原始函數是爲object類型註冊的,如果沒有找到更好的實現,則使用它。
使用dispatch()
屬性查看generic函數爲指定類型選擇哪個實現:
>>> fun.dispatch(float)
<function fun_num at 0x1035a2840>
>>> fun.dispatch(dict)
<function fun at 0x103fe0000>
使用只讀屬性registry
訪問所有註冊的實現:
>>> fun.registry.keys()
dict_keys([<class 'NoneType'>, <class 'int'>, <class 'object'>,
<class 'decimal.Decimal'>, <class 'list'>,
<class 'float'>])
>>> fun.registry[float]
<function fun_num at 0x1035a2840>
>>> fun.registry[object]
<function fun at 0x103fe0000>