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>