functools – 可调用对象的高阶函数和操作详解(2)Python语言的功能编程模块(必读进阶学习教程)(参考资料)
functools 模块用于高阶函数:作用于或返回其他函数的函数。 通常,出于此模块的目的,任何可调用对象都可以视为函数。
functools 模块定义了以下函数:
funcfunctools.
cmp_to_key
()
- 将旧式比较函数转换为键函数。 与接受键函数的工具一起使用(例如 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.
maxsize=128lru_cache(
typed=False,
)
- 装饰器用一个可保存最多 maxsize 个最近调用的可调用函数来包装一个函数。 当使用相同参数定期调用昂贵或 I/O 绑定函数时,它可以节省时间。
由于字典用于缓存结果,因此函数的位置参数和关键字参数必须是可散列的。
不同的参数模式可以被认为是具有单独缓存条目的不同调用。 例如,f(a=1, b=2) 和 f(b=2, a=1) 的关键字参数顺序不同,可能有两个单独的缓存条目。
如果 maxsize 设置为 None,LRU 功能将被禁用,缓存可以无限增长。 当 maxsize 是 2 的幂时,LRU 功能表现最佳。
如果 typed 设置为 true,不同类型的函数参数将被单独缓存。 例如,f(3) 和 f(3.0) 将被视为具有不同结果的不同调用。
为了帮助衡量缓存的有效性和调整 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 = '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数的示例 :
@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中已更改:添加了键入的选项。
@
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()))
注意
虽然这个装饰器使得创建行为良好的完全有序类型变得容易,但它确实以执行速度较慢和派生比较方法的堆栈跟踪更复杂为代价。 如果性能基准测试表明这是给定应用程序的瓶颈,那么实施所有六种丰富的比较方法可能会提供轻松的速度提升。
版本3.2中的新功能。
在 3.4 版更改:现在支持从无法识别的类型的底层比较函数返回 NotImplemented。
functools.
funcpartial(
*args,
**keywords,
)
- 返回一个新的部分对象,当调用该对象时,其行为类似于使用位置参数 args 和关键字参数 keywords 调用的 func。 如果为调用提供了更多参数,它们将附加到 args。 如果提供了额外的关键字参数,它们将扩展并覆盖关键字。 大致相当于:
def partial(func, *args, **keywords): def newfunc(*fargs, **fkeywords): newkeywords = keywords.copy() newkeywords.update(fkeywords) return func(*args, *fargs, **newkeywords) newfunc.func = func newfunc.args = args newfunc.keywords = keywords return newfunc
partial() 用于部分函数应用程序,它“冻结”函数参数和/或关键字的某些部分,从而生成具有简化签名的新对象。 例如,partial() 可用于创建一个行为类似于 int() 函数的可调用函数,其中基本参数默认为两个:
>>> from functools import partial >>> basetwo = partial(int, base=2) >>> basetwo.__doc__ = 'Convert base 2 string to an int.' >>> basetwo('10010') 18
- class
functools.
funcpartialmethod(
*args,
**keywords,
)
- 返回一个新的 partialmethod 描述符,其行为类似于 partial 除了它被设计为用作方法定义而不是直接可调用。
func 必须是描述符或可调用对象(像普通函数一样,两者的对象都作为描述符处理)。
当 func 是一个描述符(例如普通的 Python 函数、classmethod()、staticmethod()、abstractmethod() 或 partialmethod 的另一个实例)时,对 __get__ 的调用被委托给底层描述符,并返回一个适当的部分对象作为结果 .
当 func 是非描述符可调用时,将动态创建适当的绑定方法。 当用作方法时,它的行为类似于普通的 Python 函数:self 参数将作为第一个位置参数插入,甚至在提供给 partialmethod 构造函数的 args 和关键字之前。
例子:
>>> >>> 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中的新功能。
functools.
functionreduce(
iterable,
initializer[,
])
- 将两个参数的函数从左到右累加到序列的各项上,以将序列缩减为单个值。 例如,reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) 计算 ((((1+2)+3)+4)+5)。 左边的参数 x 是累加值,右边的参数 y 是序列的更新值。 如果存在可选的初始值设定项,它将在计算中放在序列项之前,并在序列为空时用作默认值。 如果未给出初始值设定项且序列仅包含一个项目,则返回第一个项目。
大致相当于:
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
@
functools.
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() 属性返回未修饰的函数,该函数启用装饰器堆叠、酸洗以及为每个变体独立创建单元测试:
>>> >>> @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 装饰的原始函数是为基本对象类型注册的,这意味着如果找不到更好的实现,就会使用它。
要检查泛型函数将为给定类型选择哪个实现,请使用 dispatch() 属性:
>>> >>> fun.dispatch(float) <function fun_num at 0x1035a2840> >>> fun.dispatch(dict) # note: default implementation <function fun at 0x103fe0000>
要访问所有已注册的实现,请使用只读注册表属性:
>>> >>> 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() 属性支持使用类型注释。
functools.
wrapperupdate_wrapper(
wrapped,
assigned=WRAPPER_ASSIGNMENTS,
updated=WRAPPER_UPDATES,
)
- 将包装函数更新为看起来像包装函数。 可选参数是元组,用于指定原始函数的哪些属性直接分配给包装函数上的匹配属性,以及包装函数的哪些属性使用原始函数的相应属性进行更新。 这些参数的默认值是模块级常量 WRAPPER_ASSIGNMENTS(分配给包装函数的 __module__、__name__、__qualname__、__annotations__ 和 __doc__,文档字符串)和 WRAPPER_UPDATES(更新包装函数的 __dict__,即实例字典)。
为了允许访问原始函数以进行内省和其他目的(例如绕过缓存装饰器,如 lru_cache()),此函数会自动将 __wrapped__ 属性添加到引用被包装函数的包装器。
此函数的主要用途是在装饰器函数中包装装饰函数并返回包装器。 如果包装函数未更新,则返回函数的元数据将反映包装定义而不是原始函数定义,这通常没有帮助。
update_wrapper() 可以与函数以外的可调用对象一起使用。 被包装的对象中缺少的任何在 assigned 或 updated 中命名的属性都将被忽略(即,此函数不会尝试在包装函数上设置它们)。 如果包装函数本身缺少更新中命名的任何属性,仍然会引发 AttributeError。
3.2 新版功能: 自动添加 __wrapped__ 属性。
3.2 新版功能: 默认复制 __annotations__ 属性。
在 3.2 版更改:缺少属性不再触发 AttributeError。
在 3.4 版更改: __wrapped__ 属性现在始终引用包装函数,即使该函数定义了 __wrapped__ 属性。
@
functools.
wrappedwraps(
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.
func
- 可调用对象或函数。 对部分对象的调用将转发给带有新参数和关键字的 func。
partial.
args
- 最左边的位置参数将被添加到提供给部分对象调用的位置参数之前。
partial.
keywords
调用部分对象时将提供的关键字参数。
部分对象类似于函数对象,因为它们是可调用的、弱引用的并且可以具有属性。 有一些重要的区别。 例如,不会自动创建 __name__ 和 __doc__ 属性。 此外,在类中定义的部分对象的行为类似于静态方法,并且在实例属性查找期间不会转换为绑定方法。