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

源代码: 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_property
    def stdev(self):
        return statistics.stdev(self._data)

它的力学原理 cached_property() 有点不同于 property() 。除非定义了setter,否则常规属性会阻止属性写入。相比之下,一个 cached_property 允许写入。

这个 cached_property Decorator仅在查找时运行,并且仅在同名属性不存在时运行。当它确实运行时, cached_property 写入同名的属性。后续属性读取和写入优先于 cached_property 方法,并且它的工作方式类似于普通属性。

可以通过删除属性来清除缓存值。这允许 cached_property 方法重新运行。

请注意,此修饰符会干扰 PEP 412 密钥共享字典。这意味着实例字典可能会比平时占用更多空间。

此外,该装饰器还要求 __dict__ 属性是可变映射。这意味着它不能与某些类型一起工作,比如元类(因为 __dict__ 类型实例上的属性是类命名空间的只读代理),以及指定 __slots__ 不包括 __dict__ 作为定义的槽之一(因为这样的类不提供 __dict__ 属性)。

如果可变映射不可用,或者需要节省空间的密钥共享,则会产生类似于 cached_property() 可以通过堆叠来实现 property() 在……顶端 cache() ::

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 .与接受关键功能的工具(例如 sorted()min()max()heapq.nlargest()heapq.nsmallest()itertools.groupby() )此函数主要用作从支持使用比较函数的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)

decorator用一个memoizing可调用文件封装一个函数 最大尺寸 最近的调用。当使用相同的参数定期调用昂贵的或I/O绑定的函数时,可以节省时间。

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

不同的参数模式可以被认为是具有单独缓存项的不同调用。例如, f(a=1, b=2)f(b=2, a=1) 关键字参数顺序不同,可能有两个单独的缓存项。

如果 user_function 指定了,它必须是可调用的。这使得 lru_cache 将直接应用于用户函数的decorator,保留 最大尺寸 默认值为128::

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

如果 最大尺寸 设置为 None ,LRU功能被禁用,缓存可以在不绑定的情况下增长。

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

包装的函数使用 cache_parameters() 返回新的 dict 显示的值 最大尺寸类型化的 . 仅供参考。改变这些值没有效果。

帮助测量缓存的有效性并调整 最大尺寸 参数,封装函数使用 cache_info() 返回一个 named tuple 显示 hits思念最大尺寸咖喱尺码 . 在多线程环境中,命中和未命中是近似值。

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

可以通过 __wrapped__ 属性。这对于自省、绕过缓存或用不同的缓存重新封装函数非常有用。

LRU (least recently used) cache 当最近的通话是即将到来的通话的最佳预测因素时(例如,新闻服务器上最流行的文章往往每天都在变化),效果最佳。缓存的大小限制确保缓存不会在没有绑定到长时间运行的进程(如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)

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

@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 版更改: 增加了 类型化的 选择权。

在 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") 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()))

注解

虽然这个修饰器可以很容易地创建行为良好、完全有序的类型,但是 does 对于派生的比较方法,以较慢的执行速度和更复杂的堆栈跟踪为代价。如果性能基准测试表明这是给定应用程序的瓶颈,那么实现所有六种丰富的比较方法可能会提供一个简单的速度引发。

注解

此修饰符不尝试重写已在类中声明的方法 或其超类 。这意味着如果超类定义了比较运算符, total_ordering 将不会再次实现它,即使原始方法是抽象的。

3.2 新版功能.

在 3.4 版更改: 对于未识别的类型,现在支持从基础比较函数返回NotImplemented。

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

返回一个新的 partial object 调用时会表现得像 func 用位置参数调用 args 和关键字参数 关键词 . 如果向调用提供了更多参数,则这些参数将附加到 args . 如果提供了其他关键字参数,它们将扩展并重写 关键词 . 大致相当于:

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, **keywords)

返回一个新的 partialmethod 行为类似的描述符 partial 但它被设计为用作方法定义,而不是直接调用。

func 必须是 descriptor 或者一个可调用的(像普通函数一样,两个对象都作为描述符处理)。

什么时候? func 是一个描述符(例如普通的python函数, classmethod()staticmethod()abstractmethod() 或其他实例 partialmethod__get__ 被委托给基础描述符,并且 partial object 作为结果返回。

什么时候? func 是非描述符可调用的,将动态创建适当的绑定方法。当用作方法时,它的行为类似于普通的python函数: self 参数将作为第一个位置参数插入,甚至在 args关键词 提供给 partialmethod 构造函数。

例子::

>>> 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的项之前,并在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() 对于生成所有中间值的迭代器。

@functools.singledispatch

将函数转换为 single-dispatch generic function .

要定义通用函数,请用 @singledispatch decorator。注意,调度发生在第一个参数的类型上,请相应地创建函数:

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

要向函数添加重载实现,请使用 register() 泛型函数的属性。它是一个装饰工。对于用类型注释的函数,decorator将自动推断第一个参数的类型::

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

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

>>> @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() 属性支持使用类型批注。

class functools.singledispatchmethod(func)

将方法转换为 single-dispatch generic function .

要定义通用方法,请用 @singledispatchmethod decorator。注意,调度发生在第一个非self或非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 必须是 最外面的 decorator。这里是 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 新版功能.

functools.update_wrapper(wrapper, wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)

更新A 封装器 函数看起来像 包裹 功能。可选参数是元组,用于指定将原始函数的哪些属性直接分配给封装函数上的匹配属性,以及使用原始函数的相应属性更新封装函数的哪些属性。这些参数的默认值是模块级常量。 WRAPPER_ASSIGNMENTS (分配给封装函数的 __module____name____qualname____annotations____doc__ ,文档字符串)和 WRAPPER_UPDATES (更新封装函数的 __dict__ 即实例字典)。

允许访问原始函数以进行自省和其他目的(例如,绕过缓存修饰符,例如 lru_cache() ,此函数自动添加 __wrapped__ 属性指向引用要封装的函数的封装器。

此功能的主要预期用途是 decorator 封装修饰函数并返回封装的函数。如果不更新封装函数,则返回函数的元数据将反映封装定义,而不是原始函数定义,这通常不太有用。

update_wrapper() 可以与函数以外的可调用文件一起使用。中命名的任何属性 分配更新 被封装的对象中缺少的将被忽略(即,此函数不会尝试在封装函数上设置它们)。 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'

如果不使用这个装饰工厂,示例函数的名称应该是 'wrapper' 和原始文档的文档字符串 example() 本来会迷路的。

partial 物体

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

partial.func

可调用的对象或函数。呼唤 partial 对象将转发到 func 使用新的参数和关键字。

partial.args

最左边的位置参数,将在提供给 partial 对象调用。

partial.keywords

partial 对象被调用。

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