functools --- 高階函數(shù)和可調(diào)用對象上的操作?

源代碼: Lib/functools.py


functools 模塊應用于高階函數(shù),即參數(shù)或(和)返回值為其他函數(shù)的函數(shù)。 通常來說,此模塊的功能適用于所有可調(diào)用對象。

functools 模塊定義了以下函數(shù):

@functools.cache(user_function)?

簡單輕量級未綁定函數(shù)緩存。 有時稱為 "memoize"。

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

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

示例:

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() 來實現(xiàn)類似 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)?

將(舊式的)比較函數(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é)果為小于則返回一個負數(shù),相等則返回零,大于則返回一個正數(shù)。key function則是一個接受一個參數(shù),并返回另一個用以排序的值的可調(diào)用對象。

示例:

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)

一個為函數(shù)提供緩存功能的裝飾器,緩存 maxsize 組傳入?yún)?shù),在下次以相同參數(shù)調(diào)用時直接返回上一次的結(jié)果。用以節(jié)約高開銷或I/O函數(shù)的調(diào)用時間。

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

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

如果指定了 user_function,它必須是一個可調(diào)用對象。 這允許 lru_cache 裝飾器被直接應用于一個用戶自定義函數(shù),讓 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) 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.

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

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

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

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

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

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

一般來說,LRU緩存只在當你想要重用之前計算的結(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 = '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ī)劃 計算 斐波那契數(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 選項。

在 3.8 版更改: 添加了 user_function 選項。

3.9 新版功能: 新增函數(shù) cache_parameters()

@functools.total_ordering?

給定一個聲明一個或多個全比較排序方法的類,這個類裝飾器實現(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)建具有良好行為的完全有序類型變得非常容易,但它 確實 是以執(zhí)行速度更緩慢和派生比較方法的堆?;厮莞鼜碗s為代價的。 如果性能基準測試表明這是特定應用的瓶頸所在,則改為實現(xiàn)全部六個富比較方法應該會輕松提升速度。

備注

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

3.2 新版功能.

在 3.4 版更改: 現(xiàn)在已支持從未識別類型的下層比較函數(shù)返回 NotImplemented 異常。

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

返回一個新的 部分對象,當被調(diào)用時其行為類似于 func 附帶位置參數(shù) args 和關鍵字參數(shù) keywords 被調(diào)用。 如果為調(diào)用提供了更多的參數(shù),它們會被附加到 args。 如果提供了額外的關鍵字參數(shù),它們會擴展并重載 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() 會被“凍結(jié)了”一部分函數(shù)參數(shù)和/或關鍵字的部分函數(shù)應用所使用,從而得到一個具有簡化簽名的新對象。 例如,partial() 可用來創(chuàng)建一個行為類似于 int() 函數(shù)的可調(diào)用對象,其中 base 參數(shù)默認為二:

>>>
>>> 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 但它被設計用作方法定義而非直接用作可調(diào)用對象。

func 必須是一個 descriptor 或可調(diào)用對象(同屬兩者的對象例如普通函數(shù)會被當作描述器來處理)。

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

func 是一個非描述器類可調(diào)用對象時,則會動態(tài)創(chuàng)建一個適當?shù)慕壎ǚ椒ā?當用作方法時其行為類似普通 Python 函數(shù):將會插入 self 參數(shù)作為第一個位置參數(shù),其位置甚至會處于提供給 partialmethod 構(gòu)造器的 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])?

將兩個參數(shù)的 function 從左至右積累地應用到 iterable 的條目,以便將該可迭代對象縮減為單一的值。 例如,reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) 是計算 ((((1+2)+3)+4)+5) 的值。 左邊的參數(shù) x 是積累值而右邊的參數(shù) 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() 了解有關可產(chǎn)生所有中間值的迭代器。

@functools.singledispatch?

將一個函數(shù)轉(zhuǎn)換為 單分派 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)
...

對于不使用類型標注的代碼,可以將適當?shù)念愋蛥?shù)顯式地傳給裝飾器本身:

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

在調(diào)用時,泛型函數(shù)會根據(jù)第一個參數(shù)的類型進行分派:

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

要訪問所有憶注冊實現(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 版更改: 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)?

將一個方法轉(zhuǎn)換為 單分派 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)?

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

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

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

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

3.2 新版功能: 自動添加 __wrapped__ 屬性。

3.2 新版功能: 默認拷貝 __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ù)和關鍵字。

partial.args?

最左邊的位置參數(shù)將放置在提供給 partial 對象調(diào)用的位置參數(shù)之前。

partial.keywords?

當調(diào)用 partial 對象時將要提供的關鍵字參數(shù)。

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