源代碼:?Lib/functools.py
functools?模塊應用于高階函數,即參數或(和)返回值為其他函數的函數。 通常來說,此模塊的功能適用于所有可調用對象。
functools?模塊定義了以下函數:
@functools.cache(user_function)
簡單輕量級未綁定函數緩存。 有時稱為?"memoize"。
返回值與?lru_cache(maxsize=None)
?相同,創建一個查找函數參數的字典的簡單包裝器。 因為它不需要移出舊值,所以比帶有大小限制的?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_propertydef stdev(self):return statistics.stdev(self._data)
cached_property()?的設定與?property()?有所不同。 常規的 property 會阻止屬性寫入,除非定義了 setter。 與之相反,cached_property?則允許寫入。
cached_property?裝飾器僅在執行查找且不存在同名屬性時才會運行。 當運行時,cached_property?會寫入同名的屬性。 后續的屬性讀取和寫入操作會優先于?cached_property?方法,其行為就像普通的屬性一樣。
緩存的值可通過刪除該屬性來清空。 這允許?cached_property?方法再次運行。
cached_property?不能防止在多線程使用中可能出現的競爭條件。 getter 函數可以在同一實例上多次運行,最后一次運行將設置緩存值。 如果緩存的特征屬性是冪等的或者對于在同一實例上多次運行是無害的,那就沒有問題。 如果需要進行同步,請在被裝飾的 getter 函數內部或在緩存的特征屬性訪問外部實現必要的鎖定操作。
注意,這個裝飾器會影響?PEP 412?鍵共享字典的操作。 這意味著相應的字典實例可能占用比通常時更多的空間。
而且,這個裝飾器要求每個實例上的?__dict__
?是可變的映射。 這意味著它將不適用于某些類型,例如元類(因為類型實例上的?__dict__
?屬性是類命名空間的只讀代理),以及那些指定了?__slots__
?但未包括?__dict__
?作為所定義的空位之一的類(因為這樣的類根本沒有提供?__dict__
?屬性)。
如果可變的映射不可用或者如果想要節省空間的鍵共享,可以通過在?lru_cache()?上堆疊?property()?來實現類似?cached_property()?的效果。 請參閱?我該如何緩存方法調用??了解這與?cached_property()?之間區別的詳情。
3.8 新版功能.
在 3.12 版更改:?在 Python 3.12 之前,cached_property
?包括了一個未寫入文檔的鎖用來確保在多線程使用中 getter 函數對于每個實例保證只運行一次。 但是,這個鎖是針對特征屬性的,不是針對實例的,這可能導致不可接受的高強度鎖爭用。 在 Python 3.12+ 中這個鎖已被移除。
functools.cmp_to_key(func)
將(舊式的)比較函數轉換為新式的?key function?. 在類似于?sorted()?,?min()?,?max()?,?heapq.nlargest()?,?heapq.nsmallest()?,?itertools.groupby()?等函數的?key?參數中使用。此函數主要用作將 Python 2 程序轉換至新版的轉換工具,以保持對比較函數的兼容。
比較函數是任何接受兩個參數,對它們進行比較,并在結果為小于時返回一個負數,相等時返回零,大于時返回一個正數的可調用對象。 鍵函數是接受一個參數并返回另一個用作排序鍵的值的可調用對象。
示例:
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?組傳入參數,在下次以相同參數調用時直接返回上一次的結果。用以節約高開銷或I/O函數的調用時間。
該緩存是線程安全的因此被包裝的函數可在多線程中使用。 這意味著下層的數據結構將在并發更新期間保持一致性。
如果另一個線程在初始調用完成并被緩存之前執行了額外的調用則被包裝的函數可能會被多次調用。
由于使用字典來緩存結果,因此傳給該函數的位置和關鍵字參數必須為?hashable。
不同的參數模式可能會被視為具有單獨緩存項的不同調用。 例如,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 特性將被禁用且緩存可無限增長。
如果?typed?被設置為 true ,不同類型的函數參數將被分別緩存。 如果?typed?為 false ,實現通常會將它們視為等價的調用,只緩存一個結果。(有些類型,如?str?和?int?,即使?typed?為 false ,也可能被分開緩存)。
注意,類型的特殊性只適用于函數的直接參數而不是它們的內容。 標量參數?Decimal(42)
?和?Fraction(42)
?被視為具有不同結果的不同調用。相比之下,元組參數?('answer',?Decimal(42))
?和?('answer',?Fraction(42))
?被視為等同的。
被包裝的函數配有一個?cache_parameters()
?函數,該函數返回一個新的?dict?用來顯示?maxsize?和?typed?的值。 這只是出于顯示信息的目的。 改變值沒有任何效果。
為了幫助衡量緩存的有效性以及調整?maxsize?形參,被包裝的函數會帶有一個?cache_info()
?函數,它返回一個?named tuple?以顯示?hits,?misses,?maxsize?和?currsize。
該裝飾器也提供了一個用于清理/使緩存失效的函數?cache_clear()
?。
原始的未經裝飾的函數可以通過?__wrapped__
?屬性訪問。它可以用于檢查、繞過緩存,或使用不同的緩存再次裝飾原始函數。
緩存會保持對參數的引用并返回值,直到它們結束生命期退出緩存或者直到緩存被清空。
如果一個方法被緩存,則?self
?實例參數會被包括在緩存中。 請參閱?我該如何緩存方法調用?
LRU(最久未使用算法)緩存?在最近的調用是即將到來的調用的最佳預測值時性能最好(例如,新聞服務器上最熱門文章傾向于每天更改)。 緩存的大小限制可確保緩存不會在長期運行進程如網站服務器上無限制地增長。
一般來說,LRU 緩存只應在你需要重復使用先前計算的值時使用。 因此,緩存有附帶影響的函數、每次調用都需要創建不同的可變對象的函數(如生成器和異步函數)或不純的函數如 time() 或 random() 等是沒有意義的。
靜態 Web 內容的 LRU 緩存示例:
@lru_cache(maxsize=32) def get_pep(num):'Retrieve text of a Python Enhancement Proposal'resource = f'https://peps.python.org/pep-{num:04d}'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 nreturn 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") andhasattr(other, "firstname"))def __eq__(self, other):if not self._is_valid_operand(other):return NotImplementedreturn ((self.lastname.lower(), self.firstname.lower()) ==(other.lastname.lower(), other.firstname.lower()))def __lt__(self, other):if not self._is_valid_operand(other):return NotImplementedreturn ((self.lastname.lower(), self.firstname.lower()) <(other.lastname.lower(), other.firstname.lower()))
備注
雖然此裝飾器使得創建具有良好行為的完全有序類型變得非常容易,但它?確實?是以執行速度更緩慢和派生比較方法的堆棧回溯更復雜為代價的。 如果性能基準測試表明這是特定應用的瓶頸所在,則改為實現全部六個富比較方法應該會輕松提升速度。
備注
這個裝飾器不會嘗試重載類?或其上級類?中已經被聲明的方法。 這意味著如果某個上級類定義了比較運算符,則?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 = funcnewfunc.args = argsnewfunc.keywords = keywordsreturn newfunc
partial()?會被“凍結了”一部分函數參數和/或關鍵字的部分函數應用所使用,從而得到一個具有簡化簽名的新對象。 例如,partial()?可用來創建一個行為類似于?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?是一個非描述器類可調用對象時,則會動態創建一個適當的綁定方法。 當用作方法時其行為類似普通 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 = initializerfor element in it:value = function(value, element)return value
請參閱?itertools.accumulate()?了解有關可產生所有中間值的迭代器。
@functools.singledispatch
將一個函數轉換為?單分派?generic function。
要定義一個泛型函數,用裝飾器?@singledispatch
?來裝飾它。當使用?@singledispatch
?定義一個函數時,請注意調度發生在第一個參數的類型上:
>>>
>>> from functools import singledispatch >>> @singledispatch ... def fun(arg, verbose=False): ... if verbose: ... print("Let me just say,", end=" ") ... print(arg)
要將重載的實現添加到函數中,請使用泛型函數的?register()
?屬性,它可以被用作裝飾器。 對于帶有類型標注的函數,該裝飾器將自動推斷第一個參數的類型:
>>>
>>> @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.UnionType?和?typing.Union:
>>>
>>> @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) ...
要啟用注冊?lambda?和現有的函數,也可以使用?register()
?屬性的函數形式:
>>>
>>> def nothing(arg, verbose=False): ... print("Nothing.") ... >>> fun.register(type(None), nothing)
register()
?屬性會返回未被裝飾的函數。 這將啟用裝飾器棧、封存,并為每個變量單獨創建單元測試:
>>>
>>> @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
在沒有針對特定類型的已注冊實現的情況下,會使用其方法解析順序來查找更通用的實現。 使用?@singledispatch
?裝飾的原始函數將為基本的?object?類型進行注冊,這意味著它將在找不到更好的實現時被使用。
如果一個實現被注冊到?abstract base class,則基類的虛擬子類將被發送到該實現:
>>>
>>> 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
要檢查泛型函數將為給定的類型選擇哪個實現,請使用?dispatch()
?屬性:
>>>
>>> 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 版更改:?register()
?屬性現在支持使用類型標注。
在 3.11 版更改:?register()
?屬性現在支持將?types.UnionType?和?typing.Union?作為類型標注。
class?functools.singledispatchmethod(func)
將一個方法轉換為?單分派?generic function。
要定義一個泛型方法,請用?@singledispatchmethod
?裝飾器來裝飾它。 當使用?@singledispatchmethod
?定義一個函數時,請注意發送操作將針對第一個非?self?或非?cls?參數的類型上:
class Negator:@singledispatchmethoddef neg(self, arg):raise NotImplementedError("Cannot negate a")@neg.registerdef _(self, arg: int):return -arg@neg.registerdef _(self, arg: bool):return not arg
@singledispatchmethod
?支持與其他裝飾器如?@classmethod?相嵌套。 請注意為了允許?dispatcher.register
,singledispatchmethod
?必須是?最外層的?裝飾器。 下面是一個?Negator
?類包含綁定到類的?neg
?方法,而不是一個類實例:
class Negator:@singledispatchmethod@classmethoddef neg(cls, arg):raise NotImplementedError("Cannot negate a")@neg.register@classmethoddef _(cls, arg: int):return -arg@neg.register@classmethoddef _(cls, arg: bool):return not arg
同樣的模式也可被用于其他類似的裝飾器:?@staticmethod,?@abstractmethod?等等。
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?中命名的任何屬性則仍將引發?AttributeError。
3.2 新版功能:?自動添加?__wrapped__
?屬性。
3.2 新版功能:?默認拷貝?__annotations__
?屬性。
在 3.2 版更改:?不存在的屬性將不再觸發?AttributeError。
在 3.4 版更改:?__wrapped__
?屬性現在總是指向被包裝的函數,即使該函數定義了?__wrapped__
?屬性。 (參見?bpo-17482)
@functools.wraps(wrapped,?assigned=WRAPPER_ASSIGNMENTS,?updated=WRAPPER_UPDATES)
這是一個便捷函數,用于在定義包裝器函數時發起調用?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 函數的名稱將變為?'wrapper'
,并且?example()
?原本的文檔字符串將會丟失。
partial?對象
partial?對象是由?partial()?創建的可調用對象。 它們具有三個只讀屬性:
partial.func
一個可調用對象或函數。 對?partial?對象的調用將被轉發給?func?并附帶新的參數和關鍵字。
partial.args
最左邊的位置參數將放置在提供給?partial?對象調用的位置參數之前。
partial.keywords
當調用?partial?對象時將要提供的關鍵字參數。
partial?對象與?function
?對象的類似之處在于它們都是可調用、可弱引用的對象并可擁有屬性。 但兩者也存在一些重要的區別。 例如前者不會自動創建?__name__?和?__doc__
?屬性。 而且,在類中定義的?partial?對象的行為類似于靜態方法,并且不會在實例屬性查找期間轉換為綁定方法。