functools --- 高階函數(shù)和可調(diào)用對象上的操作?
源代碼: Lib/functools.py
functools 模塊應(yīng)用于高階函數(shù),即——參數(shù)或(和)返回值為其他函數(shù)的函數(shù)。通常來說,此模塊的功能適用于所有可調(diào)用對象。
functools 模塊定義了以下函數(shù):
-
functools.cmp_to_key(func)? 將(舊式的)比較函數(shù)轉(zhuǎn)換為新式的 key function . 在類似于
sorted(),min(),max(),heapq.nlargest(),heapq.nsmallest(),itertools.groupby()等函數(shù)的 key 參數(shù)中使用。此函數(shù)主要用作將 Python 2 程序轉(zhuǎn)換至新版的轉(zhuǎn)換工具,以保持對比較函數(shù)的兼容。比較函數(shù)意為一個可調(diào)用對象,該對象接受兩個參數(shù)并比較它們,結(jié)果為小于則返回一個負(fù)數(shù),相等則返回零,大于則返回一個正數(shù)。key function則是一個接受一個參數(shù),并返回另一個用以排序的值的可調(diào)用對象。
示例:
sorted(iterable, key=cmp_to_key(locale.strcoll)) # locale-aware sort order
有關(guān)排序示例和簡要排序教程,請參閱 排序指南 。
3.2 新版功能.
-
@functools.lru_cache(maxsize=128, typed=False)? 一個為函數(shù)提供緩存功能的裝飾器,緩存 maxsize 組傳入?yún)?shù),在下次以相同參數(shù)調(diào)用時直接返回上一次的結(jié)果。用以節(jié)約高開銷或I/O函數(shù)的調(diào)用時間。
由于使用了字典存儲緩存,所以該函數(shù)的固定參數(shù)和關(guān)鍵字參數(shù)必須是可哈希的。
不同模式的參數(shù)可能被視為不同從而產(chǎn)生多個緩存項(xiàng),例如, f(a=1, b=2) 和 f(b=2, a=1) 因其參數(shù)順序不同,可能會被緩存兩次。
如果 maxsize 設(shè)置為
None,LRU功能將被禁用且緩存數(shù)量無上限。 maxsize 設(shè)置為2的冪時可獲得最佳性能。如果 typed 設(shè)置為true,不同類型的函數(shù)參數(shù)將被分別緩存。例如,
f(3)和f(3.0)將被視為不同而分別緩存。為了衡量緩存的有效性以便調(diào)整 maxsize 形參,被裝飾的函數(shù)帶有一個
cache_info()函數(shù)。當(dāng)調(diào)用cache_info()函數(shù)時,返回一個具名元組,包含命中次數(shù) hits,未命中次數(shù) misses ,最大緩存數(shù)量 maxsize 和 當(dāng)前緩存大小 currsize。在多線程環(huán)境中,命中數(shù)與未命中數(shù)是不完全準(zhǔn)確的。該裝飾器也提供了一個用于清理/使緩存失效的函數(shù)
cache_clear()。原始的未經(jīng)裝飾的函數(shù)可以通過
__wrapped__屬性訪問。它可以用于檢查、繞過緩存,或使用不同的緩存再次裝飾原始函數(shù)。“最久未使用算法”(LRU)緩存 在“最近的調(diào)用是即將到來的調(diào)用的最佳預(yù)測因子”時性能最好(比如,新聞服務(wù)器上最受歡迎的文章傾向于每天更改)。 “緩存大小限制”參數(shù)保證緩存不會在長時間運(yùn)行的進(jìn)程比如說網(wǎng)站服務(wù)器上無限制的增加自身的大小。
一般來說,LRU緩存只在當(dāng)你想要重用之前計算的結(jié)果時使用。因此,用它緩存具有副作用的函數(shù)、需要在每次調(diào)用時創(chuàng)建不同、易變的對象的函數(shù)或者諸如time()或random()之類的不純函數(shù)是沒有意義的。
靜態(tài) Web 內(nèi)容的 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)
以下是使用緩存通過 動態(tài)規(guī)劃 計算 斐波那契數(shù)列 的例子。
@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)
3.2 新版功能.
在 3.3 版更改: 添加 typed 選項(xiàng)。
-
@functools.total_ordering? 給定一個聲明一個或多個全比較排序方法的類,這個類裝飾器實(shí)現(xiàn)剩余的方法。這減輕了指定所有可能的全比較操作的工作。
此類必須包含以下方法之一:
__lt__()、__le__()、__gt__()或__ge__()。另外,此類必須支持__eq__()方法。例如
@total_ordering class Student: def _is_valid_operand(self, other): return (hasattr(other, "lastname") and hasattr(other, "firstname")) def __eq__(self, other): if not self._is_valid_operand(other): return NotImplemented return ((self.lastname.lower(), self.firstname.lower()) == (other.lastname.lower(), other.firstname.lower())) def __lt__(self, other): if not self._is_valid_operand(other): return NotImplemented return ((self.lastname.lower(), self.firstname.lower()) < (other.lastname.lower(), other.firstname.lower()))
注解
雖然此裝飾器使得創(chuàng)建具有良好行為的完全有序類型變得非常容易,但它 確實(shí) 是以執(zhí)行速度更緩慢和派生比較方法的堆棧回溯更復(fù)雜為代價的。 如果性能基準(zhǔn)測試表明這是特定應(yīng)用的瓶頸所在,則改為實(shí)現(xiàn)全部六個富比較方法應(yīng)該會輕松提升速度。
3.2 新版功能.
在 3.4 版更改: 現(xiàn)在已支持從未識別類型的下層比較函數(shù)返回 NotImplemented 異常。
-
functools.partial(func, *args, **keywords)? 返回一個新的 部分對象,當(dāng)被調(diào)用時其行為類似于 func 附帶位置參數(shù) args 和關(guān)鍵字參數(shù) keywords 被調(diào)用。 如果為調(diào)用提供了更多的參數(shù),它們會被附加到 args。 如果提供了額外的關(guān)鍵字參數(shù),它們會擴(kuò)展并重載 keywords。 大致等價于:
def partial(func, *args, **keywords): def newfunc(*fargs, **fkeywords): newkeywords = keywords.copy() newkeywords.update(fkeywords) return func(*args, *fargs, **newkeywords) newfunc.func = func newfunc.args = args newfunc.keywords = keywords return newfunc
partial()會被“凍結(jié)了”一部分函數(shù)參數(shù)和/或關(guān)鍵字的部分函數(shù)應(yīng)用所使用,從而得到一個具有簡化簽名的新對象。 例如,partial()可用來創(chuàng)建一個行為類似于int()函數(shù)的可調(diào)用對象,其中 base 參數(shù)默認(rèn)為二:>>> from functools import partial >>> basetwo = partial(int, base=2) >>> basetwo.__doc__ = 'Convert base 2 string to an int.' >>> basetwo('10010') 18
-
class
functools.partialmethod(func, *args, **keywords)? 返回一個新的
partialmethod描述器,其行為類似partial但它被設(shè)計用作方法定義而非直接用作可調(diào)用對象。func 必須是一個 descriptor 或可調(diào)用對象(同屬兩者的對象例如普通函數(shù)會被當(dāng)作描述器來處理)。
當(dāng) func 是一個描述器(例如普通 Python 函數(shù),
classmethod(),staticmethod(),abstractmethod()或其他partialmethod的實(shí)例)時, 對__get__的調(diào)用會被委托給底層的描述器,并會返回一個適當(dāng)?shù)?部分對象 作為結(jié)果。當(dāng) func 是一個非描述器類可調(diào)用對象時,則會動態(tài)創(chuàng)建一個適當(dāng)?shù)慕壎ǚ椒ā?當(dāng)用作方法時其行為類似普通 Python 函數(shù):將會插入 self 參數(shù)作為第一個位置參數(shù),其位置甚至?xí)幱谔峁┙o
partialmethod構(gòu)造器的 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
3.4 新版功能.
-
functools.reduce(function, iterable[, initializer])? 將兩個參數(shù)的 function 從左至右累積地應(yīng)用到 sequence 的條目,以便將該序列縮減為單一值。 例如,
reduce(lambda x, y: x+y, [1, 2, 3, 4, 5])是計算((((1+2)+3)+4)+5)的值。 左邊的參數(shù) x 是累積值而右邊的參數(shù) y 則是來自 sequence 的更新值。 如果存在可選項(xiàng) initializer,它會被放在參與計算的序列的條目之前,并在序列對象為空時作為默認(rèn)值。 如果沒有給出 initializer 并且 sequence 僅包含一個條目,則將返回第一項(xiàng)。大致相當(dāng)于:
def reduce(function, iterable, initializer=None): it = iter(iterable) if initializer is None: value = next(it) else: value = initializer for element in it: value = function(value, element) return value
-
@functools.singledispatch? 將一個函數(shù)轉(zhuǎn)換為 單分派 generic function。
要定義一個泛型函數(shù),應(yīng)使用
@singledispatch裝飾器進(jìn)行裝飾。 請注意分派是作用于第一個參數(shù)的類型,要相應(yīng)地創(chuàng)建你的函數(shù):>>> from functools import singledispatch >>> @singledispatch ... def fun(arg, verbose=False): ... if verbose: ... print("Let me just say,", end=" ") ... print(arg)
要將重載的實(shí)現(xiàn)添加到函數(shù)中,請使用泛型函數(shù)的
register()屬性。 它是一個裝飾器。 對于帶有類型標(biāo)注的函數(shù),該裝飾器將自動推斷第一個參數(shù)的類型:>>> @fun.register ... def _(arg: int, verbose=False): ... if verbose: ... print("Strength in numbers, eh?", end=" ") ... print(arg) ... >>> @fun.register ... def _(arg: list, verbose=False): ... if verbose: ... print("Enumerate this:") ... for i, elem in enumerate(arg): ... print(i, elem)
對于不使用類型標(biāo)注的代碼,可以將適當(dāng)?shù)念愋蛥?shù)顯式地傳給裝飾器本身:
>>> @fun.register(complex) ... def _(arg, verbose=False): ... if verbose: ... print("Better than complicated.", end=" ") ... print(arg.real, arg.imag) ...
要啟用注冊 lambda 和現(xiàn)有函數(shù),可以使用函數(shù)形式的
register()屬性:>>> def nothing(arg, verbose=False): ... print("Nothing.") ... >>> fun.register(type(None), nothing)
register()屬性將返回啟用了裝飾器堆棧、封存的未裝飾函數(shù),并會為每個變量單獨(dú)創(chuàng)建單元測試:>>> @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
在調(diào)用時,泛型函數(shù)會根據(jù)第一個參數(shù)的類型進(jìn)行分派:
>>> 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
在沒有用于特定類型的已注冊實(shí)現(xiàn)的情況下,則會使用其方法解析順序來查找更通用的實(shí)現(xiàn)。 以
@singledispatch裝飾的原始函數(shù)將為最基本的object類型進(jìn)行注冊,這意味著它將在找不到更好的實(shí)現(xiàn)時被使用。要檢查泛型函數(shù)將為給定類型選擇哪個實(shí)現(xiàn),請使用
dispatch()屬性:>>> fun.dispatch(float) <function fun_num at 0x1035a2840> >>> fun.dispatch(dict) # note: default implementation <function fun at 0x103fe0000>
要訪問所有憶注冊實(shí)現(xiàn),請使用只讀的
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>
3.4 新版功能.
在 3.7 版更改:
register()屬性支持使用類型標(biāo)注。
-
functools.update_wrapper(wrapper, wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)? 更新一個 wrapper 函數(shù)以使其類似于 wrapped 函數(shù)。 可選參數(shù)為指明原函數(shù)的哪些屬性要直接被賦值給 wrapper 函數(shù)的匹配屬性的元組,并且這些 wrapper 函數(shù)的屬性將使用原函數(shù)的對應(yīng)屬性來更新。 這些參數(shù)的默認(rèn)值是模塊級常量
WRAPPER_ASSIGNMENTS(它將被賦值給 wrapper 函數(shù)的__module__,__name__,__qualname__,__annotations__和__doc__即文檔字符串) 以及WRAPPER_UPDATES(它將更新 wrapper 函數(shù)的__dict__即實(shí)例字典)。為了允許出于內(nèi)省和其他目的訪問原始函數(shù)(例如繞過
lru_cache()之類的緩存裝飾器),此函數(shù)會自動為 wrapper 添加一個指向被包裝函數(shù)的__wrapped__屬性。此函數(shù)的主要目的是在 decorator 函數(shù)中用來包裝被裝飾的函數(shù)并返回包裝器。 如果包裝器函數(shù)未被更新,則被返回函數(shù)的元數(shù)據(jù)將反映包裝器定義而不是原始函數(shù)定義,這通常沒有什么用處。
update_wrapper()可以與函數(shù)之外的可調(diào)用對象一同使用。 在 assigned 或 updated 中命名的任何屬性如果不存在于被包裝對象則會被忽略(即該函數(shù)將不會嘗試在包裝器函數(shù)上設(shè)置它們)。 如果包裝器函數(shù)自身缺少在 updated 中命名的任何屬性則仍將引發(fā)AttributeError。3.2 新版功能: 自動添加
__wrapped__屬性。3.2 新版功能: 默認(rèn)拷貝
__annotations__屬性。在 3.2 版更改: 不存在的屬性將不再觸發(fā)
AttributeError。在 3.4 版更改:
__wrapped__屬性現(xiàn)在總是指向被包裝的函數(shù),即使該函數(shù)定義了__wrapped__屬性。 (參見 bpo-17482)
-
@functools.wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)? 這是一個便捷函數(shù),用于在定義包裝器函數(shù)時發(fā)起調(diào)用
update_wrapper()作為函數(shù)裝飾器。 它等價于partial(update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated)。 例如:>>> from functools import wraps >>> def my_decorator(f): ... @wraps(f) ... def wrapper(*args, **kwds): ... print('Calling decorated function') ... return f(*args, **kwds) ... return wrapper ... >>> @my_decorator ... def example(): ... """Docstring""" ... print('Called example function') ... >>> example() Calling decorated function Called example function >>> example.__name__ 'example' >>> example.__doc__ 'Docstring'
如果不使用這個裝飾器工廠函數(shù),則 example 函數(shù)的名稱將變?yōu)?
'wrapper',并且example()原本的文檔字符串將會丟失。
partial 對象?
partial 對象是由 partial() 創(chuàng)建的可調(diào)用對象。 它們具有三個只讀屬性:
-
partial.func? 一個可調(diào)用對象或函數(shù)。 對
partial對象的調(diào)用將被轉(zhuǎn)發(fā)給func并附帶新的參數(shù)和關(guān)鍵字。
partial 對象與 function 對象的類似之處在于它們都是可調(diào)用、可弱引用的對象并可擁有屬性。 但兩者也存在一些重要的區(qū)別。 例如前者不會自動創(chuàng)建 __name__ 和 __doc__ 屬性。 而且,在類中定義的 partial 對象的行為類似于靜態(tài)方法,并且不會在實(shí)例屬性查找期間轉(zhuǎn)換為綁定方法。
