functools — 可调用对象上的高阶函数和操作 — Python 文档
functools — 可调用对象上的高阶函数和操作
源代码: :source:`Lib/functools.py`
functools 模块用于高阶函数:作用于或返回其他函数的函数。 通常,出于此模块的目的,任何可调用对象都可以视为函数。
functools 模块定义了以下函数:
- @functools.cache(user_function)
简单的轻量级无界函数缓存。 有时称为 “记忆”。
返回与
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 装饰器仅在查找时运行,并且仅在不存在同名属性时运行。 当它运行时,cached_property 写入具有相同名称的属性。 后续属性读取和写入优先于 cached_property 方法,它的工作方式与普通属性一样。
可以通过删除属性来清除缓存的值。 这允许 cached_property 方法再次运行。
请注意,此装饰器会干扰 PEP 412 密钥共享字典的操作。 这意味着实例字典可能比平时占用更多空间。
此外,此装饰器要求每个实例上的
__dict__
属性是可变映射。 这意味着它不适用于某些类型,例如元类(因为类型实例上的__dict__
属性是类命名空间的只读代理),以及那些指定__slots__
而不包含 [ X214X] 作为定义的插槽之一(因为此类类根本不提供__dict__
属性)。如果可变映射不可用或需要节省空间的密钥共享,则可以通过在 [X192X 之上堆叠 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)
将旧式比较函数转换为 键函数 。 与接受关键函数的工具一起使用(例如 sorted()、min()、max()、heapq.nlargest() ]、heapq.nsmallest()、itertools.groupby())。 此函数主要用作从支持使用比较函数的 Python 2 转换而来的程序的转换工具。
比较函数是任何可调用的函数,它接受两个参数,对它们进行比较,并在小于时返回负数,相等时返回零,或大于时返回正数。 键函数是一个可调用的函数,它接受一个参数并返回另一个用作排序键的值。
例子:
sorted(iterable, key=cmp_to_key(locale.strcoll)) # locale-aware sort order
有关排序示例和简短的排序教程,请参阅 Sorting HOW TO。
3.2 版中的新功能。
- @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) 的关键字参数顺序不同,可能有两个单独的缓存条目。
如果指定了 user_function,则它必须是可调用的。 这允许将 lru_cache 装饰器直接应用于用户函数,将 maxsize 保留为其默认值 128:
@lru_cache def count_vowels(sentence): return sum(sentence.count(vowel) for vowel in 'AEIOUaeiou')
如果maxsize设置为
None
,LRU特性被禁用,缓存可以无限制增长。如果 typed 设置为 true,不同类型的函数参数将被分别缓存。 如果 typed 为 false,则实现通常会将它们视为等效调用并且仅缓存单个结果。 (某些类型如 str 和 int 可能会被单独缓存,即使 typed 为 false。)
请注意,类型特异性仅适用于函数的直接参数而不是它们的内容。 标量参数
Decimal(42)
和Fraction(42)
被视为具有不同结果的不同调用。 相反,元组参数('answer', Decimal(42))
和('answer', Fraction(42))
被视为等价的。封装的函数使用
cache_parameters()
函数进行检测,该函数返回一个新的 dict,显示 maxsize 和 typed 的值。 这仅供参考。 改变值没有效果。为了帮助衡量缓存的有效性并调整 maxsize 参数,包装函数使用
cache_info()
函数进行检测,该函数返回一个名为 的元组 显示 命中、 未命中 、maxsize 和 currsize。装饰器还提供了一个
cache_clear()
函数来清除或使缓存失效。原始底层函数可通过
__wrapped__
属性访问。 这对于内省、绕过缓存或使用不同的缓存重新包装函数很有用。缓存保留对参数和返回值的引用,直到它们从缓存中老化或直到缓存被清除。
LRU(最近最少使用)缓存 在最近的调用是即将到来的调用的最佳预测指标时效果最好(例如,新闻服务器上最流行的文章往往每天都在变化)。 缓存的大小限制确保缓存不会在不受长时间运行的进程(例如 Web 服务器)的限制的情况下增长。
一般来说,LRU 缓存应该只在您想要重用以前计算的值时使用。 因此,缓存具有副作用的函数、需要在每次调用时创建不同可变对象的函数或诸如 time() 或 random() 之类的不纯函数是没有意义的。
静态网页内容的 LRU 缓存示例:
@lru_cache(maxsize=32) def get_pep(num): 'Retrieve text of a Python Enhancement Proposal' resource = 'https://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)
使用缓存高效计算 斐波那契数 以实现 动态规划 技术的示例:
@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()))
笔记
虽然这个装饰器可以很容易地创建表现良好的全序类型,但它 确实 以执行较慢和派生比较方法的更复杂堆栈跟踪为代价。 如果性能基准测试表明这是给定应用程序的瓶颈,那么实现所有六种丰富的比较方法可能会提供简单的速度提升。
笔记
此装饰器不会尝试覆盖已在类 或其超类 中声明的方法。 这意味着如果超类定义了一个比较运算符,即使原始方法是抽象的,total_ordering 也不会再次实现它。
3.2 版中的新功能。
3.4 版更改: 现在支持从无法识别的类型的底层比较函数返回 NotImplemented。
- functools.partial(func, /, *args, **keywords)
返回一个新的 部分对象 ,它在调用时的行为类似于使用位置参数 args 和关键字参数 keywords 调用的 func。 如果为调用提供了更多参数,则将它们附加到 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 必须是一个 描述符 或一个可调用对象(与普通函数一样的对象都作为描述符处理)。
当 func 是一个描述符时(比如一个普通的 Python 函数,classmethod(), staticmethod(),
abstractmethod()
的另一个实例X152X]partialmethod),对__get__
的调用被委托给底层描述符,并返回一个适当的 部分对象 作为结果。当 func 是非描述符可调用时,会动态创建适当的绑定方法。 这在用作方法时表现得像一个普通的 Python 函数: self 参数将作为第一个位置参数插入,甚至在提供的 args 和 keywords 之前到 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的项上,从而将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
-
要定义通用函数,请使用
@singledispatch
装饰器对其进行装饰。 使用@singledispatch
定义函数时,请注意分派发生在第一个参数的类型上:>>> from functools import singledispatch >>> @singledispatch ... def fun(arg, verbose=False): ... if verbose: ... print("Let me just say,", end=" ") ... print(arg)
要向函数添加重载实现,请使用泛型函数的
register()
属性,该属性可用作装饰器。 对于用类型注释的函数,装饰器会自动推断第一个参数的类型:>>> @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) ...
要启用注册 lambdas 和预先存在的函数,还可以以函数形式使用
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
如果特定类型没有注册实现,则使用其方法解析顺序来查找更通用的实现。 用
@singledispatch
修饰的原始函数是为基本 object 类型注册的,这意味着如果找不到更好的实现,就会使用它。如果实现注册到 抽象基类 ,基类的虚拟子类将被分派到该实现:
>>> 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)
-
要定义通用方法,请使用
@singledispatchmethod
装饰器对其进行装饰。 使用@singledispatchmethod
定义函数时,请注意分派发生在第一个非 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.register
,singledispatchmethod
必须是 最外层 装饰器。 这是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
相同的模式可用于其他类似的装饰器:@staticmethod、@abstractmethod 等。
3.8 版中的新功能。
- functools.update_wrapper(wrapper, wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)
将 wrapper 函数更新为 wrapped 函数。 可选参数是元组,用于指定将原始函数的哪些属性直接分配给包装函数上的匹配属性,以及使用原始函数的相应属性更新包装函数的哪些属性。 这些参数的默认值是模块级常量
WRAPPER_ASSIGNMENTS
(分配给包装函数的__module__
、__name__
、__qualname__
、__annotations__
] 和__doc__
,文档字符串)和WRAPPER_UPDATES
(更新包装函数的__dict__
,即 实例字典)。允许出于内省和其他目的访问原始函数(例如 绕过缓存装饰器,例如 lru_cache()),该函数会自动将
__wrapped__
属性添加到引用被包装函数的包装器。此函数的主要用途是在 decorator 函数中,这些函数包装装饰函数并返回包装器。 如果包装函数没有更新,返回函数的元数据将反映包装定义而不是原始函数定义,这通常没有帮助。
update_wrapper() 可以与函数以外的可调用对象一起使用。 assigned 或 updated 中命名的任何属性在被包装的对象中缺失的都将被忽略(即 此函数不会尝试在包装函数上设置它们)。 如果包装函数本身缺少 updated 中命名的任何属性,仍会引发 AttributeError。
3.2新功能:自动添加
__wrapped__
属性。3.2 新功能:默认复制
__annotations__
属性。3.2 版更改: 缺少属性不再触发 AttributeError 。
3.4 版更改:
__wrapped__
属性现在总是指包装的函数,即使该函数定义了__wrapped__
属性。 (见 :issue:`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()
的文档字符串将丢失。