functools --- 高階函數和可調用對象上的操作?
源代碼: Lib/functools.py
functools 模塊應用于高階函數,即參數或(和)返回值為其他函數的函數。 通常來說,此模塊的功能適用于所有可調用對象。
functools 模塊定義了以下函數:
- @functools.cache(user_function)?
簡單輕量級未綁定函數緩存。 有時稱為 "memoize"。
返回值與
lru_cache(maxsize=None)相同,創(chuàng)建一個查找函數參數的字典的簡單包裝器。 因為它不需要移出舊值,所以比帶有大小限制的lru_cache()更小更快。例如:
@cache def factorial(n): return n * factorial(n-1) if n else 1 >>> factorial(10) # no previously cached result, makes 11 recursive calls 3628800 >>> factorial(5) # just looks up cached value result 120 >>> factorial(12) # makes two new recursive calls, the other 10 are cached 479001600
3.9 新版功能.
- @functools.cached_property(func)?
將一個類方法轉換為特征屬性,一次性計算該特征屬性的值,然后將其緩存為實例生命周期內的普通屬性。 類似于
property()但增加了緩存功能。 對于在其他情況下實際不可變的高計算資源消耗的實例特征屬性來說該函數非常有用。示例:
class DataSet: def __init__(self, sequence_of_numbers): self._data = tuple(sequence_of_numbers) @cached_property def stdev(self): return statistics.stdev(self._data)
cached_property()的設定與property()有所不同。 常規(guī)的 property 會阻止屬性寫入,除非定義了 setter。 與之相反,cached_property 則允許寫入。cached_property 裝飾器僅在執(zhí)行查找且不存在同名屬性時才會運行。 當運行時,cached_property 會寫入同名的屬性。 后續(xù)的屬性讀取和寫入操作會優(yōu)先于 cached_property 方法,其行為就像普通的屬性一樣。
緩存的值可通過刪除該屬性來清空。 這允許 cached_property 方法再次運行。
注意,這個裝飾器會影響 PEP 412 鍵共享字典的操作。 這意味著相應的字典實例可能占用比通常時更多的空間。
而且,這個裝飾器要求每個實例上的
__dict__是可變的映射。 這意味著它將不適用于某些類型,例如元類(因為類型實例上的__dict__屬性是類命名空間的只讀代理),以及那些指定了__slots__但未包括__dict__作為所定義的空位之一的類(因為這樣的類根本沒有提供__dict__屬性)。如果可變的映射不可用或者如果想要節(jié)省空間的鍵共享,可以通過在
cache()之上堆疊一個property()來實現類似cached_property()的效果:class DataSet: def __init__(self, sequence_of_numbers): self._data = sequence_of_numbers @property @cache def stdev(self): return statistics.stdev(self._data)
3.8 新版功能.
- functools.cmp_to_key(func)?
將(舊式的)比較函數轉換為新式的 key function . 在類似于
sorted(),min(),max(),heapq.nlargest(),heapq.nsmallest(),itertools.groupby()等函數的 key 參數中使用。此函數主要用作將 Python 2 程序轉換至新版的轉換工具,以保持對比較函數的兼容。比較函數意為一個可調用對象,該對象接受兩個參數并比較它們,結果為小于則返回一個負數,相等則返回零,大于則返回一個正數。key function則是一個接受一個參數,并返回另一個用以排序的值的可調用對象。
示例:
sorted(iterable, key=cmp_to_key(locale.strcoll)) # locale-aware sort order
有關排序示例和簡要排序教程,請參閱 排序指南 。
3.2 新版功能.
- @functools.lru_cache(user_function)?
- @functools.lru_cache(maxsize=128, typed=False)
一個為函數提供緩存功能的裝飾器,緩存 maxsize 組傳入參數,在下次以相同參數調用時直接返回上一次的結果。用以節(jié)約高開銷或I/O函數的調用時間。
由于使用了字典存儲緩存,所以該函數的固定參數和關鍵字參數必須是可哈希的。
不同模式的參數可能被視為不同從而產生多個緩存項,例如, f(a=1, b=2) 和 f(b=2, a=1) 因其參數順序不同,可能會被緩存兩次。
如果指定了 user_function,它必須是一個可調用對象。 這允許 lru_cache 裝飾器被直接應用于一個用戶自定義函數,讓 maxsize 保持其默認值 128:
@lru_cache def count_vowels(sentence): return sum(sentence.count(vowel) for vowel in 'AEIOUaeiou')
如果 maxsize 設為
None,LRU 特性將被禁用且緩存可無限增長。If typed is set to true, function arguments of different types will be cached separately. If typed is false, the implementation will usually regard them as equivalent calls and only cache a single result. (Some types such as str and int may be cached separately even when typed is false.)
Note, type specificity applies only to the function's immediate arguments rather than their contents. The scalar arguments,
Decimal(42)andFraction(42)are be treated as distinct calls with distinct results. In contrast, the tuple arguments('answer', Decimal(42))and('answer', Fraction(42))are treated as equivalent.被包裝的函數配有一個
cache_parameters()函數,該函數返回一個新的dict用來顯示 maxsize 和 typed 的值。 這只是出于顯示信息的目的。 改變值沒有任何效果。為了幫助衡量緩存的有效性以及調整 maxsize 形參,被包裝的函數會帶有一個
cache_info()函數,它返回一個 named tuple 以顯示 hits, misses, maxsize 和 currsize。該裝飾器也提供了一個用于清理/使緩存失效的函數
cache_clear()。原始的未經裝飾的函數可以通過
__wrapped__屬性訪問。它可以用于檢查、繞過緩存,或使用不同的緩存再次裝飾原始函數。緩存會保持對參數的引用并返回值,直到它們結束生命期退出緩存或者直到緩存被清空。
LRU(最久未使用算法)緩存 在最近的調用是即將到來的調用的最佳預測值時性能最好(例如,新聞服務器上最熱門文章傾向于每天更改)。 緩存的大小限制可確保緩存不會在長期運行進程如網站服務器上無限制地增長。
一般來說,LRU緩存只在當你想要重用之前計算的結果時使用。因此,用它緩存具有副作用的函數、需要在每次調用時創(chuàng)建不同、易變的對象的函數或者諸如time()或random()之類的不純函數是沒有意義的。
靜態(tài) Web 內容的 LRU 緩存示例:
@lru_cache(maxsize=32) def get_pep(num): 'Retrieve text of a Python Enhancement Proposal' resource = 'https://peps.python.org/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ī)劃 計算 斐波那契數列 的例子。
@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 選項。
在 3.8 版更改: 添加了 user_function 選項。
3.9 新版功能: 新增函數
cache_parameters()
- @functools.total_ordering?
給定一個聲明一個或多個全比較排序方法的類,這個類裝飾器實現剩余的方法。這減輕了指定所有可能的全比較操作的工作。
此類必須包含以下方法之一:
__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)建具有良好行為的完全有序類型變得非常容易,但它 確實 是以執(zhí)行速度更緩慢和派生比較方法的堆?;厮莞鼜碗s為代價的。 如果性能基準測試表明這是特定應用的瓶頸所在,則改為實現全部六個富比較方法應該會輕松提升速度。
備注
這個裝飾器不會嘗試重載類 或其上級類 中已經被聲明的方法。 這意味著如果某個上級類定義了比較運算符,則 total_ordering 將不會再次實現它,即使原方法是抽象方法。
3.2 新版功能.
在 3.4 版更改: 現在已支持從未識別類型的下層比較函數返回 NotImplemented 異常。
- functools.partial(func, /, *args, **keywords)?
返回一個新的 部分對象,當被調用時其行為類似于 func 附帶位置參數 args 和關鍵字參數 keywords 被調用。 如果為調用提供了更多的參數,它們會被附加到 args。 如果提供了額外的關鍵字參數,它們會擴展并重載 keywords。 大致等價于:
def partial(func, /, *args, **keywords): def newfunc(*fargs, **fkeywords): newkeywords = {**keywords, **fkeywords} return func(*args, *fargs, **newkeywords) newfunc.func = func newfunc.args = args newfunc.keywords = keywords return newfunc
partial()會被“凍結了”一部分函數參數和/或關鍵字的部分函數應用所使用,從而得到一個具有簡化簽名的新對象。 例如,partial()可用來創(chuàng)建一個行為類似于int()函數的可調用對象,其中 base 參數默認為二:>>> 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但它被設計用作方法定義而非直接用作可調用對象。func 必須是一個 descriptor 或可調用對象(同屬兩者的對象例如普通函數會被當作描述器來處理)。
當 func 是一個描述器(例如普通 Python 函數,
classmethod(),staticmethod(),abstractmethod()或其他partialmethod的實例)時, 對__get__的調用會被委托給底層的描述器,并會返回一個適當的 部分對象 作為結果。當 func 是一個非描述器類可調用對象時,則會動態(tài)創(chuàng)建一個適當的綁定方法。 當用作方法時其行為類似普通 Python 函數:將會插入 self 參數作為第一個位置參數,其位置甚至會處于提供給
partialmethod構造器的 args 和 keywords 之前。示例:
>>> class Cell: ... 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])?
將兩個參數的 function 從左至右積累地應用到 iterable 的條目,以便將該可迭代對象縮減為單一的值。 例如,
reduce(lambda x, y: x+y, [1, 2, 3, 4, 5])是計算((((1+2)+3)+4)+5)的值。 左邊的參數 x 是積累值而右邊的參數 y 則是來自 iterable 的更新值。 如果存在可選項 initializer,它會被放在參與計算的可迭代對象的條目之前,并在可迭代對象為空時作為默認值。 如果沒有給出 initializer 并且 iterable 僅包含一個條目,則將返回第一項。大致相當于:
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
請參閱
itertools.accumulate()了解有關可產生所有中間值的迭代器。
- @functools.singledispatch?
將一個函數轉換為 單分派 generic function。
To define a generic function, decorate it with the
@singledispatchdecorator. When defining a function using@singledispatch, note that the dispatch happens on the type of the first argument:>>> from functools import singledispatch >>> @singledispatch ... def fun(arg, verbose=False): ... if verbose: ... print("Let me just say,", end=" ") ... print(arg)
To add overloaded implementations to the function, use the
register()attribute of the generic function, which can be used as a decorator. For functions annotated with types, the decorator will infer the type of the first argument automatically:>>> @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)
types.UnionTypeandtyping.Unioncan also be used:>>> @fun.register ... def _(arg: int | float, verbose=False): ... if verbose: ... print("Strength in numbers, eh?", end=" ") ... print(arg) ... >>> from typing import Union >>> @fun.register ... def _(arg: Union[list, set], verbose=False): ... if verbose: ... print("Enumerate this:") ... for i, elem in enumerate(arg): ... print(i, elem) ...
對于不使用類型標注的代碼,可以將適當的類型參數顯式地傳給裝飾器本身:
>>> @fun.register(complex) ... def _(arg, verbose=False): ... if verbose: ... print("Better than complicated.", end=" ") ... print(arg.real, arg.imag) ...
To enable registering lambdas and pre-existing functions, the
register()attribute can also be used in a functional form:>>> def nothing(arg, verbose=False): ... print("Nothing.") ... >>> fun.register(type(None), nothing)
The
register()attribute returns the undecorated function. This enables decorator stacking,pickling, and the creation of unit tests for each variant independently:>>> @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
在調用時,泛型函數會根據第一個參數的類型進行分派:
>>> 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
Where there is no registered implementation for a specific type, its method resolution order is used to find a more generic implementation. The original function decorated with
@singledispatchis registered for the baseobjecttype, which means it is used if no better implementation is found.If an implementation is registered to an abstract base class, virtual subclasses of the base class will be dispatched to that implementation:
>>> from collections.abc import Mapping >>> @fun.register ... def _(arg: Mapping, verbose=False): ... if verbose: ... print("Keys & Values") ... for key, value in arg.items(): ... print(key, "=>", value) ... >>> fun({"a": "b"}) a => b
To check which implementation the generic function will choose for a given type, use the
dispatch()attribute:>>> fun.dispatch(float) <function fun_num at 0x1035a2840> >>> fun.dispatch(dict) # note: default implementation <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>
3.4 新版功能.
在 3.7 版更改: The
register()attribute now supports using type annotations.在 3.11 版更改: The
register()attribute now supportstypes.UnionTypeandtyping.Unionas type annotations.
- class functools.singledispatchmethod(func)?
將一個方法轉換為 單分派 generic function。
To define a generic method, decorate it with the
@singledispatchmethoddecorator. When defining a function using@singledispatchmethod, note that the dispatch happens on the type of the first non-self or non-cls argument:class Negator: @singledispatchmethod def neg(self, arg): raise NotImplementedError("Cannot negate a") @neg.register def _(self, arg: int): return -arg @neg.register def _(self, arg: bool): return not arg
@singledispatchmethodsupports nesting with other decorators such as@classmethod. Note that to allow fordispatcher.register,singledispatchmethodmust be the outer most decorator. Here is theNegatorclass with thenegmethods bound to the class, rather than an instance of the class:class Negator: @singledispatchmethod @classmethod def neg(cls, arg): raise NotImplementedError("Cannot negate a") @neg.register @classmethod def _(cls, arg: int): return -arg @neg.register @classmethod def _(cls, arg: bool): return not arg
The same pattern can be used for other similar decorators:
@staticmethod,@abstractmethod, and others.3.8 新版功能.
- functools.update_wrapper(wrapper, wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)?
更新一個 wrapper 函數以使其類似于 wrapped 函數。 可選參數為指明原函數的哪些屬性要直接被賦值給 wrapper 函數的匹配屬性的元組,并且這些 wrapper 函數的屬性將使用原函數的對應屬性來更新。 這些參數的默認值是模塊級常量
WRAPPER_ASSIGNMENTS(它將被賦值給 wrapper 函數的__module__,__name__,__qualname__,__annotations__和__doc__即文檔字符串) 以及WRAPPER_UPDATES(它將更新 wrapper 函數的__dict__即實例字典)。為了允許出于內省和其他目的訪問原始函數(例如繞過
lru_cache()之類的緩存裝飾器),此函數會自動為 wrapper 添加一個指向被包裝函數的__wrapped__屬性。此函數的主要目的是在 decorator 函數中用來包裝被裝飾的函數并返回包裝器。 如果包裝器函數未被更新,則被返回函數的元數據將反映包裝器定義而不是原始函數定義,這通常沒有什么用處。
update_wrapper()可以與函數之外的可調用對象一同使用。 在 assigned 或 updated 中命名的任何屬性如果不存在于被包裝對象則會被忽略(即該函數將不會嘗試在包裝器函數上設置它們)。 如果包裝器函數自身缺少在 updated 中命名的任何屬性則仍將引發(fā)AttributeError。3.2 新版功能: 自動添加
__wrapped__屬性。3.2 新版功能: 默認拷貝
__annotations__屬性。在 3.2 版更改: 不存在的屬性將不再觸發(fā)
AttributeError。在 3.4 版更改:
__wrapped__屬性現在總是指向被包裝的函數,即使該函數定義了__wrapped__屬性。 (參見 bpo-17482)
- @functools.wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)?
這是一個便捷函數,用于在定義包裝器函數時發(fā)起調用
update_wrapper()作為函數裝飾器。 它等價于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'
如果不使用這個裝飾器工廠函數,則 example 函數的名稱將變?yōu)?
'wrapper',并且example()原本的文檔字符串將會丟失。
partial 對象?
partial 對象是由 partial() 創(chuàng)建的可調用對象。 它們具有三個只讀屬性:
partial 對象與 function 對象的類似之處在于它們都是可調用、可弱引用的對象并可擁有屬性。 但兩者也存在一些重要的區(qū)別。 例如前者不會自動創(chuàng)建 __name__ 和 __doc__ 屬性。 而且,在類中定義的 partial 對象的行為類似于靜態(tài)方法,并且不會在實例屬性查找期間轉換為綁定方法。