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
The cache is threadsafe so the wrapped function can be used in multiple threads.
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()
有所不同。 常规的 property 会阻止属性写入,除非定义了 setter。 与之相反,cached_property 则允许写入。cached_property 装饰器仅在执行查找且不存在同名属性时才会运行。 当运行时,cached_property 会写入同名的属性。 后续的属性读取和写入操作会优先于 cached_property 方法,其行为就像普通的属性一样。
缓存的值可通过删除该属性来清空。 这允许 cached_property 方法再次运行。
注意,这个装饰器会影响 PEP 412 键共享字典的操作。 这意味着相应的字典实例可能占用比通常时更多的空间。
而且,这个装饰器要求每个实例上的
__dict__
是可变的映射。 这意味着它将不适用于某些类型,例如元类(因为类型实例上的__dict__
属性是类命名空间的只读代理),以及那些指定了__slots__
但未包括__dict__
作为所定义的空位之一的类(因为这样的类根本没有提供__dict__
属性)。如果可变的映射不可用或者如果想要节省空间的键共享,可以通过在
cache()
之上堆叠一个property()
来实现类似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 . 在类似于
sorted()
,min()
,max()
,heapq.nlargest()
,heapq.nsmallest()
,itertools.groupby()
等函数的 key 参数中使用。此函数主要用作将 Python 2 程序转换至新版的转换工具,以保持对比较函数的兼容。A comparison function is any callable that accepts two arguments, compares them, and returns a negative number for less-than, zero for equality, or a positive number for greater-than. A key function is a callable that accepts one argument and returns another value to be used as the sort key.
示例:
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)
一个为函数提供缓存功能的装饰器,缓存 maxsize 组传入参数,在下次以相同参数调用时直接返回上一次的结果。用以节约高开销或I/O函数的调用时间。
The cache is threadsafe so the wrapped function can be used in multiple threads.
由于使用了字典存储缓存,所以该函数的固定参数和关键字参数必须是可哈希的。
Distinct argument patterns may be considered to be distinct calls with separate cache entries. For example,
f(a=1, b=2)
andf(b=2, a=1)
differ in their keyword argument order and may have two separate cache entries.如果指定了 user_function,它必须是一个可调用对象。 这允许 lru_cache 装饰器被直接应用于一个用户自定义函数,让 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)
andFraction(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.被包装的函数配有一个
cache_parameters()
函数,该函数返回一个新的dict
用来显示 maxsize 和 typed 的值。 这只是出于显示信息的目的。 改变值没有任何效果。为了帮助衡量缓存的有效性以及调整 maxsize 形参,被包装的函数会带有一个
cache_info()
函数,它返回一个 named tuple 以显示 hits, misses, maxsize 和 currsize。该装饰器也提供了一个用于清理/使缓存失效的函数
cache_clear()
。原始的未经装饰的函数可以通过
__wrapped__
属性访问。它可以用于检查、绕过缓存,或使用不同的缓存再次装饰原始函数。缓存会保持对参数的引用并返回值,直到它们结束生命期退出缓存或者直到缓存被清空。
If a method is cached, the
self
instance argument is included in the cache. See 我该如何缓存方法调用?LRU(最久未使用算法)缓存 在最近的调用是即将到来的调用的最佳预测值时性能最好(例如,新闻服务器上最热门文章倾向于每天更改)。 缓存的大小限制可确保缓存不会在长期运行进程如网站服务器上无限制地增长。
一般来说,LRU缓存只在当你想要重用之前计算的结果时使用。因此,用它缓存具有副作用的函数、需要在每次调用时创建不同、易变的对象的函数或者诸如time()或random()之类的不纯函数是没有意义的。
静态 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)
@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¶
给定一个声明一个或多个全比较排序方法的类,这个类装饰器实现剩余的方法。这减轻了指定所有可能的全比较操作的工作。
此类必须包含以下方法之一:
__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()))
备注
虽然此装饰器使得创建具有良好行为的完全有序类型变得非常容易,但它 确实 是以执行速度更缓慢和派生比较方法的堆栈回溯更复杂为代价的。 如果性能基准测试表明这是特定应用的瓶颈所在,则改为实现全部六个富比较方法应该会轻松提升速度。
备注
这个装饰器不会尝试重载类 或其上级类 中已经被声明的方法。 这意味着如果某个上级类定义了比较运算符,则 total_ordering 将不会再次实现它,即使原方法是抽象方法。
3.2 新版功能.
在 3.4 版更改: 现在已支持从未识别类型的下层比较函数返回 NotImplemented 异常。
- functools.partial(func, /, *args, **keywords)¶
返回一个新的 部分对象,当被调用时其行为类似于 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, **keywords)¶
返回一个新的
partialmethod
描述器,其行为类似partial
但它被设计用作方法定义而非直接用作可调用对象。func 必须是一个 descriptor 或可调用对象(同属两者的对象例如普通函数会被当作描述器来处理)。
当 func 是一个描述器(例如普通 Python 函数,
classmethod()
,staticmethod()
,abstractmethod()
或其他partialmethod
的实例)时, 对__get__
的调用会被委托给底层的描述器,并会返回一个适当的 部分对象 作为结果。当 func 是一个非描述器类可调用对象时,则会动态创建一个适当的绑定方法。 当用作方法时其行为类似普通 Python 函数:将会插入 self 参数作为第一个位置参数,其位置甚至会处于提供给
partialmethod
构造器的 args 和 keywords 之前。示例:
>>> 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 的更新值。 如果存在可选项 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()
了解有关可产生所有中间值的迭代器。
- @functools.singledispatch¶
将一个函数转换为 单分派 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
andtyping.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) ...
对于不使用类型标注的代码,可以将适当的类型参数显式地传给装饰器本身:
>>> @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
在调用时,泛型函数会根据第一个参数的类型进行分派:
>>> 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 baseobject
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>
要访问所有忆注册实现,请使用只读的
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 supportstypes.UnionType
andtyping.Union
as type annotations.
- class functools.singledispatchmethod(func)¶
将一个方法转换为 单分派 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 fordispatcher.register
,singledispatchmethod
must be the outer most decorator. Here is theNegator
class with theneg
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 函数以使其类似于 wrapped 函数。 可选参数为指明原函数的哪些属性要直接被赋值给 wrapper 函数的匹配属性的元组,并且这些 wrapper 函数的属性将使用原函数的对应属性来更新。 这些参数的默认值是模块级常量
WRAPPER_ASSIGNMENTS
(它将被赋值给 wrapper 函数的__module__
,__name__
,__qualname__
,__annotations__
和__doc__
即文档字符串) 以及WRAPPER_UPDATES
(它将更新 wrapper 函数的__dict__
即实例字典)。为了允许出于内省和其他目的访问原始函数(例如绕过
lru_cache()
之类的缓存装饰器),此函数会自动为 wrapper 添加一个指向被包装函数的__wrapped__
属性。此函数的主要目的是在 decorator 函数中用来包装被装饰的函数并返回包装器。 如果包装器函数未被更新,则被返回函数的元数据将反映包装器定义而不是原始函数定义,这通常没有什么用处。
update_wrapper()
可以与函数之外的可调用对象一同使用。 在 assigned 或 updated 中命名的任何属性如果不存在于被包装对象则会被忽略(即该函数将不会尝试在包装器函数上设置它们)。 如果包装器函数自身缺少在 updated 中命名的任何属性则仍将引发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'
如果不使用这个装饰器工厂函数,则 example 函数的名称将变为
'wrapper'
,并且example()
原本的文档字符串将会丢失。
partial
对象¶
partial
对象是由 partial()
创建的可调用对象。 它们具有三个只读属性:
partial
对象与 function
对象的类似之处在于它们都是可调用、可弱引用的对象并可拥有属性。 但两者也存在一些重要的区别。 例如前者不会自动创建 __name__
和 __doc__
属性。 而且,在类中定义的 partial
对象的行为类似于静态方法,并且不会在实例属性查找期间转换为绑定方法。