functools --- 高階函數和可調用對象上的操作?

源代碼: Lib/functools.py


functools 模塊應用于高階函數,即參數或(和)返回值為其他函數的函數。 通常來(lái)說(shuō),此模塊的功能適用于所有可調用對象。

functools 模塊定義了以下函數:

@functools.cache(user_function)?

簡(jiǎn)單輕量級未綁定函數緩存。 有時(shí)稱(chēng)為 "memoize"。

返回值與 lru_cache(maxsize=None) 相同,創(chuàng )建一個(gè)查找函數參數的字典的簡(jiǎn)單包裝器。 因為它不需要移出舊值,所以比帶有大小限制的 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)?

將一個(gè)類(lèi)方法轉換為特征屬性,一次性計算該特征屬性的值,然后將其緩存為實(shí)例生命周期內的普通屬性。 類(lèi)似于 property() 但增加了緩存功能。 對于在其他情況下實(shí)際不可變的高計算資源消耗的實(shí)例特征屬性來(lái)說(shuō)該函數非常有用。

示例:

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() 有所不同。 常規的 property 會(huì )阻止屬性寫(xiě)入,除非定義了 setter。 與之相反,cached_property 則允許寫(xiě)入。

cached_property 裝飾器僅在執行查找且不存在同名屬性時(shí)才會(huì )運行。 當運行時(shí),cached_property 會(huì )寫(xiě)入同名的屬性。 后續的屬性讀取和寫(xiě)入操作會(huì )優(yōu)先于 cached_property 方法,其行為就像普通的屬性一樣。

緩存的值可通過(guò)刪除該屬性來(lái)清空。 這允許 cached_property 方法再次運行。

注意,這個(gè)裝飾器會(huì )影響 PEP 412 鍵共享字典的操作。 這意味著(zhù)相應的字典實(shí)例可能占用比通常時(shí)更多的空間。

而且,這個(gè)裝飾器要求每個(gè)實(shí)例上的 __dict__ 是可變的映射。 這意味著(zhù)它將不適用于某些類(lèi)型,例如元類(lèi)(因為類(lèi)型實(shí)例上的 __dict__ 屬性是類(lèi)命名空間的只讀代理),以及那些指定了 __slots__ 但未包括 __dict__ 作為所定義的空位之一的類(lèi)(因為這樣的類(lèi)根本沒(méi)有提供 __dict__ 屬性)。

如果可變的映射不可用或者如果想要節省空間的鍵共享,可以通過(guò)在 cache() 之上堆疊一個(gè) property() 來(lái)實(shí)現類(lèi)似 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 . 在類(lèi)似于 sorted() , min() , max() , heapq.nlargest() , heapq.nsmallest() , itertools.groupby() 等函數的 key 參數中使用。此函數主要用作將 Python 2 程序轉換至新版的轉換工具,以保持對比較函數的兼容。

比較函數意為一個(gè)可調用對象,該對象接受兩個(gè)參數并比較它們,結果為小于則返回一個(gè)負數,相等則返回零,大于則返回一個(gè)正數。key function則是一個(gè)接受一個(gè)參數,并返回另一個(gè)用以排序的值的可調用對象。

示例:

sorted(iterable, key=cmp_to_key(locale.strcoll))  # locale-aware sort order

有關(guān)排序示例和簡(jiǎn)要排序教程,請參閱 排序指南 。

3.2 新版功能.

@functools.lru_cache(user_function)?
@functools.lru_cache(maxsize=128, typed=False)

一個(gè)為函數提供緩存功能的裝飾器,緩存 maxsize 組傳入參數,在下次以相同參數調用時(shí)直接返回上一次的結果。用以節約高開(kāi)銷(xiāo)或I/O函數的調用時(shí)間。

由于使用了字典存儲緩存,所以該函數的固定參數和關(guān)鍵字參數必須是可哈希的。

不同模式的參數可能被視為不同從而產(chǎn)生多個(gè)緩存項,例如, f(a=1, b=2)f(b=2, a=1) 因其參數順序不同,可能會(huì )被緩存兩次。

如果指定了 user_function,它必須是一個(gè)可調用對象。 這允許 lru_cache 裝飾器被直接應用于一個(gè)用戶(hù)自定義函數,讓 maxsize 保持其默認值 128:

@lru_cache
def count_vowels(sentence):
    return sum(sentence.count(vowel) for vowel in 'AEIOUaeiou')

如果 maxsize 設為 None,LRU 特性將被禁用且緩存可無(wú)限增長(cháng)。

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) and Fraction(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.

被包裝的函數配有一個(gè) cache_parameters() 函數,該函數返回一個(gè)新的 dict 用來(lái)顯示 maxsizetyped 的值。 這只是出于顯示信息的目的。 改變值沒(méi)有任何效果。

為了幫助衡量緩存的有效性以及調整 maxsize 形參,被包裝的函數會(huì )帶有一個(gè) cache_info() 函數,它返回一個(gè) named tuple 以顯示 hits, misses, maxsizecurrsize。

該裝飾器也提供了一個(gè)用于清理/使緩存失效的函數 cache_clear() 。

原始的未經(jīng)裝飾的函數可以通過(guò) __wrapped__ 屬性訪(fǎng)問(wèn)。它可以用于檢查、繞過(guò)緩存,或使用不同的緩存再次裝飾原始函數。

緩存會(huì )保持對參數的引用并返回值,直到它們結束生命期退出緩存或者直到緩存被清空。

LRU(最久未使用算法)緩存 在最近的調用是即將到來(lái)的調用的最佳預測值時(shí)性能最好(例如,新聞服務(wù)器上最熱門(mén)文章傾向于每天更改)。 緩存的大小限制可確保緩存不會(huì )在長(cháng)期運行進(jìn)程如網(wǎng)站服務(wù)器上無(wú)限制地增長(cháng)。

一般來(lái)說(shuō),LRU緩存只在當你想要重用之前計算的結果時(shí)使用。因此,用它緩存具有副作用的函數、需要在每次調用時(shí)創(chuàng )建不同、易變的對象的函數或者諸如time()或random()之類(lèi)的不純函數是沒(méi)有意義的。

靜態(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)

以下是使用緩存通過(guò) 動(dòng)態(tài)規劃 計算 斐波那契數列 的例子。

@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?

給定一個(gè)聲明一個(gè)或多個(gè)全比較排序方法的類(lèi),這個(gè)類(lèi)裝飾器實(shí)現剩余的方法。這減輕了指定所有可能的全比較操作的工作。

此類(lèi)必須包含以下方法之一:__lt__() 、__le__()、__gt__()__ge__()。另外,此類(lèi)必須支持 __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 )建具有良好行為的完全有序類(lèi)型變得非常容易,但它 確實(shí) 是以執行速度更緩慢和派生比較方法的堆?;厮莞鼜碗s為代價(jià)的。 如果性能基準測試表明這是特定應用的瓶頸所在,則改為實(shí)現全部六個(gè)富比較方法應該會(huì )輕松提升速度。

備注

這個(gè)裝飾器不會(huì )嘗試重載類(lèi) 或其上級類(lèi) 中已經(jīng)被聲明的方法。 這意味著(zhù)如果某個(gè)上級類(lèi)定義了比較運算符,則 total_ordering 將不會(huì )再次實(shí)現它,即使原方法是抽象方法。

3.2 新版功能.

在 3.4 版更改: 現在已支持從未識別類(lèi)型的下層比較函數返回 NotImplemented 異常。

functools.partial(func, /, *args, **keywords)?

返回一個(gè)新的 部分對象,當被調用時(shí)其行為類(lèi)似于 func 附帶位置參數 args 和關(guān)鍵字參數 keywords 被調用。 如果為調用提供了更多的參數,它們會(huì )被附加到 args。 如果提供了額外的關(guān)鍵字參數,它們會(huì )擴展并重載 keywords。 大致等價(jià)于:

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() 會(huì )被“凍結了”一部分函數參數和/或關(guān)鍵字的部分函數應用所使用,從而得到一個(gè)具有簡(jiǎn)化簽名的新對象。 例如,partial() 可用來(lái)創(chuàng )建一個(gè)行為類(lèi)似于 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)?

返回一個(gè)新的 partialmethod 描述器,其行為類(lèi)似 partial 但它被設計用作方法定義而非直接用作可調用對象。

func 必須是一個(gè) descriptor 或可調用對象(同屬兩者的對象例如普通函數會(huì )被當作描述器來(lái)處理)。

func 是一個(gè)描述器(例如普通 Python 函數, classmethod(), staticmethod(), abstractmethod() 或其他 partialmethod 的實(shí)例)時(shí), 對 __get__ 的調用會(huì )被委托給底層的描述器,并會(huì )返回一個(gè)適當的 部分對象 作為結果。

func 是一個(gè)非描述器類(lèi)可調用對象時(shí),則會(huì )動(dòng)態(tài)創(chuàng )建一個(gè)適當的綁定方法。 當用作方法時(shí)其行為類(lèi)似普通 Python 函數:將會(huì )插入 self 參數作為第一個(gè)位置參數,其位置甚至會(huì )處于提供給 partialmethod 構造器的 argskeywords 之前。

示例:

>>>
>>> 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])?

將兩個(gè)參數的 function 從左至右積累地應用到 iterable 的條目,以便將該可迭代對象縮減為單一的值。 例如,reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) 是計算 ((((1+2)+3)+4)+5) 的值。 左邊的參數 x 是積累值而右邊的參數 y 則是來(lái)自 iterable 的更新值。 如果存在可選項 initializer,它會(huì )被放在參與計算的可迭代對象的條目之前,并在可迭代對象為空時(shí)作為默認值。 如果沒(méi)有給出 initializer 并且 iterable 僅包含一個(gè)條目,則將返回第一項。

大致相當于:

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() 了解有關(guān)可產(chǎn)生所有中間值的迭代器。

@functools.singledispatch?

將一個(gè)函數轉換為 單分派 generic function。

To define a generic function, decorate it with the @singledispatch decorator. 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.UnionType and typing.Union can 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)
...

對于不使用類(lèi)型標注的代碼,可以將適當的類(lèi)型參數顯式地傳給裝飾器本身:

>>>
>>> @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

在調用時(shí),泛型函數會(huì )根據第一個(gè)參數的類(lèi)型進(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

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 @singledispatch is registered for the base object type, 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>

要訪(fǎng)問(wèn)所有憶注冊實(shí)現,請使用只讀的 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 supports types.UnionType and typing.Union as type annotations.

class functools.singledispatchmethod(func)?

將一個(gè)方法轉換為 單分派 generic function。

To define a generic method, decorate it with the @singledispatchmethod decorator. 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

@singledispatchmethod supports nesting with other decorators such as @classmethod. Note that to allow for dispatcher.register, singledispatchmethod must be the outer most decorator. Here is the Negator class with the neg methods 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)?

更新一個(gè) wrapper 函數以使其類(lèi)似于 wrapped 函數。 可選參數為指明原函數的哪些屬性要直接被賦值給 wrapper 函數的匹配屬性的元組,并且這些 wrapper 函數的屬性將使用原函數的對應屬性來(lái)更新。 這些參數的默認值是模塊級常量 WRAPPER_ASSIGNMENTS (它將被賦值給 wrapper 函數的 __module__, __name__, __qualname__, __annotations____doc__ 即文檔字符串) 以及 WRAPPER_UPDATES (它將更新 wrapper 函數的 __dict__ 即實(shí)例字典)。

為了允許出于內省和其他目的訪(fǎng)問(wèn)原始函數(例如繞過(guò) lru_cache() 之類(lèi)的緩存裝飾器),此函數會(huì )自動(dòng)為 wrapper 添加一個(gè)指向被包裝函數的 __wrapped__ 屬性。

此函數的主要目的是在 decorator 函數中用來(lái)包裝被裝飾的函數并返回包裝器。 如果包裝器函數未被更新,則被返回函數的元數據將反映包裝器定義而不是原始函數定義,這通常沒(méi)有什么用處。

update_wrapper() 可以與函數之外的可調用對象一同使用。 在 assignedupdated 中命名的任何屬性如果不存在于被包裝對象則會(huì )被忽略(即該函數將不會(huì )嘗試在包裝器函數上設置它們)。 如果包裝器函數自身缺少在 updated 中命名的任何屬性則仍將引發(fā) AttributeError。

3.2 新版功能: 自動(dòng)添加 __wrapped__ 屬性。

3.2 新版功能: 默認拷貝 __annotations__ 屬性。

在 3.2 版更改: 不存在的屬性將不再觸發(fā) AttributeError。

在 3.4 版更改: __wrapped__ 屬性現在總是指向被包裝的函數,即使該函數定義了 __wrapped__ 屬性。 (參見(jiàn) bpo-17482)

@functools.wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)?

這是一個(gè)便捷函數,用于在定義包裝器函數時(shí)發(fā)起調用 update_wrapper() 作為函數裝飾器。 它等價(jià)于 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'

如果不使用這個(gè)裝飾器工廠(chǎng)函數,則 example 函數的名稱(chēng)將變?yōu)?'wrapper',并且 example() 原本的文檔字符串將會(huì )丟失。

partial 對象?

partial 對象是由 partial() 創(chuàng )建的可調用對象。 它們具有三個(gè)只讀屬性:

partial.func?

一個(gè)可調用對象或函數。 對 partial 對象的調用將被轉發(fā)給 func 并附帶新的參數和關(guān)鍵字。

partial.args?

最左邊的位置參數將放置在提供給 partial 對象調用的位置參數之前。

partial.keywords?

當調用 partial 對象時(shí)將要提供的關(guān)鍵字參數。

partial 對象與 function 對象的類(lèi)似之處在于它們都是可調用、可弱引用的對象并可擁有屬性。 但兩者也存在一些重要的區別。 例如前者不會(huì )自動(dòng)創(chuàng )建 __name____doc__ 屬性。 而且,在類(lèi)中定義的 partial 對象的行為類(lèi)似于靜態(tài)方法,并且不會(huì )在實(shí)例屬性查找期間轉換為綁定方法。