Skip to main content

10.2. functools —对可调用对象的高阶函数和操作

源代码: Lib/functools.py


functools 模块用于高阶函数:作用于或返回其他函数的函数。一般来说,任何可调用对象都可以被视为用于此模块的函数。

functools 模块定义以下功能:

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(maxsize=128, typed=False)

装饰器使用memoize可调用函数来包装函数,以节省最多的 maxsize 最近调用。当使用相同的参数定期调用昂贵的或I/O绑定的函数时,可以节省时间。

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

如果 maxsize 设置为 None,则禁用LRU功能,并且缓存可以无限制增长。当 maxsize 是二的幂时,LRU特征执行得最好。

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

为了帮助测量高速缓存的有效性并调整 maxsize 参数,使用返回显示 hitsmissesmaxsizecurrsizenamed tuplecache_info() 函数来测试包装的函数。在多线程环境中,命中和未命中是近似值。

装饰器还提供用于清除或使高速缓存无效的 cache_clear() 功能。

原始的底层函数可以通过 __wrapped__ 属性访问。这对于内省,绕过缓存或者用不同的缓存重新封装函数很有用。

当最近的呼叫是即将来电的最佳预测因子时(例如,新闻服务器上最受欢迎的文章趋向于每天改变),LRU(最近最少使用)缓存 工作得最好。缓存的大小限制确保缓存不会在长期运行的进程(如Web服务器)上不受限制地增长。

静态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)

使用高速缓存来有效地计算 斐波纳契数 以实现 动态规划 技术的示例:

@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 选项。

@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 以导致比较方法的执行速度较慢和更复杂的堆栈跟踪为代价。如果性能基准测试表明这是给定应用程序的瓶颈,则实施所有六种丰富的比较方法可能提供一个容易的速度提升。

3.2 新版功能.

在 3.4 版更改: 现在支持从无法识别的类型的基础比较函数返回未实现。

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

返回一个新的 partial 对象,当被调用时,它的行为将像 func 调用位置参数 args 和关键字参数 keywords。如果向调用提供了更多参数,则将它们附加到 args。如果提供了其他关键字参数,它们将扩展并覆盖 keywords。大致相当于:

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() 函数的可调用,其中 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 对象作为结果。

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

例:

>>> 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.reduce(function, iterable[, initializer])

function 的两个参数累加应用于 sequence 的项,从左到右,以便将序列减少为单个值。例如,reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) 计算 ((((1+2)+3)+4)+5)。左参数 x 是累加值,右参数 y 是来自 sequence 的更新值。如果可选 initializer 存在,则它将放在计算中序列的项目之前,并在序列为空时用作默认值。如果未给出 initializer 并且 sequence 仅包含一个项,则返回第一个项。

大致相当于:

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(default)

将函数转换为 单调 generic function

要定义通用函数,请使用 @singledispatch 装饰器进行装饰。请注意,分派发生在第一个参数的类型上,因此创建您的函数:

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

要向函数添加重载的实现,请使用通用函数的 register() 属性。它是一个装饰器,采用一个类型参数并装饰实现该类型操作的函数:

>>> @fun.register(int)
... def _(arg, verbose=False):
...     if verbose:
...         print("Strength in numbers, eh?", end=" ")
...     print(arg)
...
>>> @fun.register(list)
... def _(arg, verbose=False):
...     if verbose:
...         print("Enumerate this:")
...     for i, elem in enumerate(arg):
...         print(i, elem)

要启用注册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 类型,这意味着如果没有找到更好的实现,则使用它。

要检查通用函数为给定类型选择的实现,请使用 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 新版功能.

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() 可以与除函数之外的可调用项一起使用。忽略正在包装的对象中缺少的 assignedupdated 中命名的任何属性(即此函数不会尝试在包装函数中设置它们)。如果包装函数本身缺少 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() 的文档字符串将丢失。

10.2.1. partial 对象

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

partial.func

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

partial.args

最左边的位置参数将被添加到提供给 partial 对象调用的位置参数。

partial.keywords

调用 partial 对象时将提供的关键字参数。

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