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)造器的 argskeywords 之前。

示例:

>>> 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)用對象一同使用。 在 assignedupdated 中命名的任何屬性如果不存在于被包裝對象則會被忽略(即該函數(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.args?

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

partial.keywords?

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

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