functools
— 可调用对象上的高阶函数和操作¶
源代码: 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 方法。
cached_property 不能防止多线程使用中可能发生的竞争条件。getter 函数可能会在同一实例上运行多次,最近一次运行会设置缓存的值。如果缓存的属性是幂等的,或者多次在实例上运行不会造成危害,那么这没问题。如果需要同步,请在修饰的 getter 函数内部或缓存的属性访问周围实现必要的锁定。
注意,此装饰器会干扰 PEP 412 密钥共享字典的操作。这意味着实例字典可能会占用比平时更多的空间。
此外,此装饰器要求每个实例上的
__dict__
属性是可变的映射。这意味着它将无法与某些类型一起使用,例如元类(因为类型实例上的__dict__
属性是类命名空间的只读代理),以及那些指定__slots__
但不包括__dict__
作为已定义槽之一的类型(因为此类根本不提供__dict__
属性)。如果可变映射不可用或需要节省空间的密钥共享,则可以通过在
lru_cache()
之上堆叠property()
来实现类似于cached_property()
的效果。有关这与cached_property()
有何不同的更多详细信息,请参阅 如何缓存方法调用?。在 3.8 版本中添加。
在 3.12 版本中更改: 在 Python 3.12 之前,
cached_property
包含一个未记录的锁,以确保在多线程使用中,getter 函数保证每个实例只运行一次。但是,锁是按属性而不是按实例的,这可能会导致不可接受的高锁争用。在 Python 3.12+ 中,此锁定已移除。
- 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
有关排序示例和简短的排序教程,请参阅 排序技术。
在 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,则实现通常会将它们视为等效调用,并且仅缓存单个结果。(即使 typed 为 false,某些类型(例如 str 和 int)也可能被单独缓存。)
请注意,类型特异性仅适用于函数的直接参数,而不是它们的内容。标量参数
Decimal(42)
和Fraction(42)
将被视为具有不同结果的不同调用。相反,元组参数('answer', Decimal(42))
和('answer', Fraction(42))
将被视为等效。包装后的函数配备了一个
cache_parameters()
函数,该函数返回一个新的dict
,其中显示了 maxsize 和 typed 的值。这仅用于信息目的。更改这些值无效。为了帮助衡量缓存的效率并调整 maxsize 参数,包装后的函数配备了一个
cache_info()
函数,该函数返回一个 命名元组,显示 hits、misses、maxsize 和 currsize。装饰器还提供了一个
cache_clear()
函数,用于清除或使缓存无效。原始的基础函数可以通过
__wrapped__
属性访问。这对于内省、绕过缓存或使用不同的缓存重新包装函数很有用。缓存保留对参数和返回值的引用,直到它们从缓存中过期或直到缓存被清除。
如果缓存了一个方法,则
self
实例参数将包含在缓存中。请参阅如何缓存方法调用?LRU (最近最少使用) 缓存在最近的调用是即将到来的调用的最佳预测器时效果最佳(例如,新闻服务器上最受欢迎的文章每天都会发生变化)。缓存的大小限制确保缓存在长时间运行的进程(如 Web 服务器)上不会无限增长。
通常,LRU 缓存仅应在您想重用先前计算的值时使用。因此,缓存具有副作用的函数、需要在每次调用时创建不同的可变对象的函数(例如生成器和异步函数)或诸如 time() 或 random() 之类的非纯函数没有意义。
静态 Web 内容的 LRU 缓存示例
@lru_cache(maxsize=32) def get_pep(num): 'Retrieve text of a Python Enhancement Proposal' resource = f'https://peps.pythonlang.cn/pep-{num:04d}' 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)¶
返回一个新的partial 对象,该对象在调用时将像调用带有位置参数 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 参数默认为 2>>> 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()
或partialmethod
的另一个实例)时,对__get__
的调用将委托给底层描述符,并返回一个适当的partial 对象作为结果。当 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, [initial, ]/)¶
从左到右累积地将 function 应用于 iterable 的项,以便将可迭代对象缩减为单个值。例如,
reduce(lambda x, y: x+y, [1, 2, 3, 4, 5])
计算((((1+2)+3)+4)+5)
。左侧参数 x 是累积值,右侧参数 y 是来自 iterable 的更新值。如果存在可选的 initial,它将在计算中放置在可迭代对象的项之前,并在可迭代对象为空时作为默认值。如果未提供 initial 且 iterable 仅包含一项,则返回第一项。大致等同于
initial_missing = object() def reduce(function, iterable, initial=initial_missing, /): it = iter(iterable) if initial is initial_missing: value = next(it) else: value = initial 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)
types.UnionType
和typing.Union
也可以使用>>> @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) ...
对于在集合类型(例如
list
)上分派,但希望类型提示集合的项(例如list[int]
)的代码,应将分派类型显式传递给装饰器本身,并将类型提示放入函数定义中>>> @fun.register(list) ... def _(arg: list[int], verbose=False): ... if verbose: ... print("Enumerate this:") ... for i, elem in enumerate(arg): ... print(i, elem)
注意
在运行时,该函数将分派到列表的实例,而不管列表中包含的类型如何,即
[1,2,3]
将与["foo", "bar", "baz"]
以相同的方式分派。此示例中提供的注解仅用于静态类型检查器,没有运行时影响。要启用注册 lambda 和预先存在的函数,也可以使用函数形式的
register()
属性>>> def nothing(arg, verbose=False): ... print("Nothing.") ... >>> fun.register(type(None), nothing)
register()
属性返回未装饰的函数。这使得可以进行装饰器堆叠、pickle
以及为每个变体独立创建单元测试>>> @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()
属性现在支持使用类型注解。在 3.11 版本中更改:
register()
属性现在支持types.UnionType
和typing.Union
作为类型注解。
- 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__
、__type_params__
和__doc__
(文档字符串))和WRAPPER_UPDATES
(它更新包装器函数的__dict__
,即实例字典)。为了允许访问原始函数以进行内省和其他目的(例如,绕过诸如
lru_cache()
之类的缓存装饰器),此函数会自动向包装器添加一个__wrapped__
属性,该属性引用被包装的函数。此函数的主要用途是在 装饰器 函数中,这些函数包装被装饰的函数并返回包装器。如果包装器函数未更新,则返回函数的元数据将反映包装器定义而不是原始函数定义,这通常不太有帮助。
update_wrapper()
可以与函数以外的可调用对象一起使用。任何在 assigned 或 updated 中命名的属性如果被包装的对象中缺少,则会被忽略(即,此函数不会尝试在包装器函数上设置它们)。如果包装器函数本身缺少 updated 中命名的任何属性,则仍然会引发AttributeError
。3.2 版本更改: 现在会自动添加
__wrapped__
属性。__annotations__
属性现在默认会被复制。 缺少属性不再触发AttributeError
异常。3.4 版本更改:
__wrapped__
属性现在始终指向被包装的函数,即使该函数定义了__wrapped__
属性。(参见 bpo-17482)3.12 版本更改:
__type_params__
属性现在默认会被复制。
- @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
对象类似于 函数对象,它们都是可调用的,弱可引用的,并且可以有属性。但也有一些重要的区别。例如,__name__
和 __doc__
属性不会自动创建。此外,在类中定义的 partial
对象行为类似于静态方法,并且在实例属性查找期间不会转换为绑定方法。