functools-可调用对象上的高阶函数和操作

源代码: Lib/functools.py


functools模块用于高阶函数:作用于或返回其他函数的函数。通常,就此模块而言,任何可调用对象都可以视为函数。

functools模块定义以下Function:

  • @ functools. cached_property(* func *)
    • 将类的方法转换为属性,该属性的值将被计算一次,然后在实例生命周期中作为常规属性进行缓存。与property()相似,但增加了缓存。对于实例有效的不可变的昂贵的计算属性很有用。

Example:

class DataSet:
    def __init__(self, sequence_of_numbers):
        self._data = sequence_of_numbers

    @cached_property
    def stdev(self):
        return statistics.stdev(self._data)

    @cached_property
    def variance(self):
        return statistics.variance(self._data)

3.8 版的新Function。

Note

此装饰器要求每个实例上的__dict__属性是可变 Map。这意味着它不适用于某些类型,例如元类(因为类型实例上的__dict__属性是类名称空间的只读代理),以及指定__slots__而不将__dict__作为定义的插槽之一的类(例如此类)根本不提供__dict__属性)。

比较函数是可以接受两个参数,对其进行比较并返回小于(小于)的负数,等于(等于)的零或大于(大于)的正数的任何可调用函数。键函数是一个可调用函数,它接受一个参数并返回另一个值用作排序键。

Example:

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

有关排序示例和简短的排序教程,请参见排序方式

3.2 版中的新Function。

  • @ functools. lru_cache(* user_function *)

  • @ functools. lru_cache(* maxsize = 128 typed = False *)

    • 装饰器将带有回忆的可调用函数包装为函数,最多可保存* maxsize *个最近的调用。当使用相同的参数定期调用昂贵的或 I/O 绑定的函数时,可以节省时间。

由于使用字典来缓存结果,因此该函数的位置和关键字参数必须是可哈希的。

可以将不同的参数模式视为具有单独的缓存项的不同调用。例如,f(a = 1,b = 2)和 f(b = 2,a = 1)的关键字参数 Sequences 不同,并且可能有两个单独的缓存条目。

如果指定了* user_function ,则它必须是可调用的。这允许 lru_cache 装饰器直接应用于用户函数,而将 maxsize *保留为其默认值 128:

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

如果* maxsize *设置为None,则 LRU Function被禁用,并且缓存可以无限增长。

如果* typed *设置为 true,则将分别缓存不同类型的函数参数。例如,f(3)f(3.0)将被视为具有不同结果的不同调用。

为了帮助衡量缓存的有效性并调整* maxsize 参数,包装的函数配有cache_info()函数,该函数返回named tuple,显示 hits misses maxsize currsize *。在多线程环境中,命中率和未命中率是近似的。

装饰器还提供cache_clear()函数,用于清除或使缓存无效。

原始基础Function可pass__wrapped__属性访问。这对于自省,绕过缓存或用其他缓存重新包装Function很有用。

当最近的呼叫是即将到来的呼叫的最佳预测器(例如,新闻服务器上最受欢迎的文章往往每天都在变化)时,LRU(最近最少使用)缓存的效果最佳。缓存的大小限制确保了缓存不会在不受限制的长时间运行的进程(例如 Web 服务器)上增长。

通常,仅当您要重用先前计算的值时,才应使用 LRU 缓存。因此,缓存具有副作用的函数,需要在每次调用时创建不同的可变对象的函数或不纯函数(例如 time()或 random())是没有意义的。

用于静态 Web 内容的 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)

使用高速缓存来实现dynamic programming技术的高效计算Fibonacci numbers的示例:

@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 版中的新Function。

在版本 3.3 中进行了更改:添加了* typed *选项。

在 3.8 版中进行了更改:添加了* user_function *选项。

  • @ functools. total_ordering
    • 给定一个类定义一个或多个丰富的比较排序方法,该类装饰器提供其余的内容。这简化了指定所有可能的丰富比较操作的工作:

该类必须定义lt()le()gt()ge()之一。另外,该类应提供eq()方法。

For example:

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

Note

尽管此装饰器使创建行为良好的完全有序类型变得容易,但这样做确实是以执行速度较慢和派生比较方法的堆栈跟踪更复杂为代价的。如果性能基准测试表明这是给定应用程序的瓶颈,则实施所有六种丰富的比较方法可能会轻松提高速度。

3.2 版中的新Function。

在版本 3.4 中进行了更改:现在支持从基础比较函数为无法识别的类型返回 NotImplemented。

  • functools. partial(* func /*, *args 关键字)
    • 返回一个新的partial object,该partial object在被调用时的行为类似于* 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()可用于创建行为类似于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 关键字)
    • 返回一个行为与partial相似的新partialmethodDescriptors,除了它被设计为用作方法定义而不是可直接调用之外。
  • func *必须是descriptor或可调用的(与正常函数一样,这两个对象都作为 Descriptors 处理)。

如果* func *是 Descriptors(例如普通的 Python 函数classmethod()staticmethod()abstractmethod()partialmethod的另一个实例),则对__get__的调用将委派给基础 Descriptors,并返回相应的partial object作为结果。

当* func 是非 Descriptors 可调用时,将动态创建适当的绑定方法。当用作方法时,其行为类似于普通的 Python 函数: self 参数将作为第一个位置参数插入,甚至在提供给partialmethod构造函数的 args keywords *之前。

Example:

>>> 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 版的新Function。

  • functools. reduce(* function iterable * [,* initializer *])
    • 从左到右,将两个参数的* function 累计应用于 iterable 的项,以将 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()

要定义通用函数,请使用@singledispatch装饰器对其进行装饰。注意,分派发生在第一个参数的类型上,相应地创建函数:

>>> from functools import singledispatch
>>> @singledispatch
... def fun(arg, verbose=False):
...     if verbose:
...         print("Let me just say,", end=" ")
...     print(arg)

要将重载的实现添加到函数中,请使用泛型函数的register()属性。它是一个装饰。对于带有类型 Comments 的函数,装饰器将自动推断第一个参数的类型:

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

对于不使用类型 Comments 的代码,可以将适当的类型参数显式传递给装饰器本身:

>>> @fun.register(complex)
... def _(arg, verbose=False):
...     if verbose:
...         print("Better than complicated.", end=" ")
...     print(arg.real, arg.imag)
...

要启用注册 lambda 和预先存在的Function,可以以Function形式使用register()属性:

>>> def nothing(arg, verbose=False):
...     print("Nothing.")
...
>>> fun.register(type(None), nothing)

register()属性返回未修饰的函数,该函数启用装饰器堆栈,Pickling 以及为每个变量独立创建单元测试:

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

如果没有针对特定类型的注册实现,则使用其方法解析 Sequences 来查找更通用的实现。以@singledispatch修饰的原始函数已注册为基本object类型,这意味着如果找不到更好的实现,则使用该函数。

要检查通用函数将为给定类型选择哪种实现,请使用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 版的新Function。

在版本 3.7 中更改:register()属性支持使用类型 Comments。

要定义通用方法,请使用@singledispatchmethod装饰器对其进行装饰。请注意,调度是根据第一个非自身或非 cls 参数的类型进行的,请相应地创建函数:

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支持与其他装饰器(例如@classmethod)嵌套。请注意,要允许dispatcher.registersingledispatchmethod必须是最外装饰器。这是Negator类,其中neg方法受类限制:

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

可以将相同的模式用于其他类似的修饰符:staticmethodabstractmethod等。

3.8 版的新Function。

  • functools. update_wrapper(* wrapper wrapped assigned = WRAPPER_ASSIGNMENTS updated = WRAPPER_UPDATES *)
    • 更新* wrapper 函数,使其看起来像 wrapped *函数。可选参数是 Tuples,用于指定将原始函数的哪些属性直接分配给包装函数上的匹配属性,以及使用原始函数中的相应属性更新包装函数的哪些属性。这些参数的默认值是模块级常量WRAPPER_ASSIGNMENTS(分配给包装函数的__module____name____qualname____annotations____doc__(文档字符串))和WRAPPER_UPDATES(更新包装函数的__dict__,即实例字典)。

为了允许出于自省和其他目的访问原始函数(例如,绕过诸如lru_cache()之类的缓存修饰符),此函数会自动向包装器添加__wrapped__属性,该属性引用要包装的函数。

此函数的主要用途是在decorator函数中,该函数包装装饰后的函数并返回包装器。如果包装器函数未更新,则返回的函数的元数据将反映包装器定义,而不是原始函数定义,这通常会有所帮助。

update_wrapper()可以与函数以外的其他可调用对象一起使用。被包装的对象中缺少的* assigned updated 中命名的任何属性都将被忽略(即,此函数将不会try在包装函数上设置它们)。如果包装函数本身缺少 updated *中命名的任何属性,则仍会引发AttributeError

版本 3.2 中的新Function:自动添加__wrapped__属性。

3.2 版中的新Function:默认情况下,复制__annotations__属性。

在版本 3.2 中更改:缺少属性不再触发AttributeError

在版本 3.4 中进行了更改:__wrapped__属性现在始终引用包装的函数,即使该函数定义了__wrapped__属性也是如此。 (请参见bpo-17482)

  • @ functools. wraps(包装,*已分配= 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'

如果不使用此装饰器工厂,则示例函数的名称将为'wrapper',而原始example()的文档字符串将丢失。

partial Objects

partial对象是partial()创建的可调用对象。它们具有三个只读属性:

  • partial. func

    • 可调用的对象或函数。带有新参数和关键字的对partial对象的调用将被转发到func
  • partial. args

    • 最左侧的位置参数将放在提供给partial对象调用的位置参数之前。
  • partial. keywords

    • 调用partial对象时将提供的关键字参数。

partial对象类似于function对象,因为它们是可调用的,弱引用的,并且可以具有属性。有一些重要的区别。例如,不会自动创建name__doc__属性。同样,在类中定义的partial对象的行为类似于静态方法,并且在实例属性查找期间不会转换为绑定方法。