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

源代码: Lib / functools.py

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

functools 模块定义了以下函数︰

functools.cmp_to_key(func)

将旧风格的比较函数转换为key函数用于接受key函数的工具(例如sorted()min()max()heapq.nlargest )heapq.nsmallest()itertools.groupby())。此函数主要用作从支持使用比较函数的Python 2转换的程序的过渡工具。

比较函数是任何一个可调用的函数,且包含两个参数,对参数进行比较,如果小于返回负数,等于返回0,大于返回正数。Key函数是一个可调用对象,它接受一个参数并返回另一个值作为排序的键。

示例:

sorted(iterable, key=cmp_to_key(locale.strcoll))  # locale-aware sort order

有关排序示例和简要排序教程,请参阅排序HOWTO

版本3.2中的新功能。

@functools.lru_cache(maxsize=128, typed=False)

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

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

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

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

To help measure the effectiveness of the cache and tune the maxsize parameter, the wrapped function is instrumented with a cache_info() function that returns a named tuple showing hits, misses, maxsize and currsize. 在多线程环境中,命中和未命中是近似的。

装饰器还提供了用于清除或使缓存无效的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中更改:添加了类型选项。

@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中更改:现在支持从无法识别的类型的基础比较函数返回未实现。

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

返回一个新的partial对象,该对象在调用时将采用位置参数args和关键字参数关键字调用的func如果提供多个参数调用, 它们会被追加给 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或可调用对象(这两个对象都像常规函数一样被处理为描述器)。

When func is a descriptor (such as a normal Python function, classmethod(), staticmethod(), abstractmethod() or another instance of partialmethod), calls to __get__ are delegated to the underlying descriptor, and an appropriate partial object returned as the result.

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

示例:

>>> 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])

将两个参数的函数累加到序列的项中,从左到右,以便将序列减少为单个值。For example, reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) calculates ((((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(default)

将函数转换为single-dispatch 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()属性返回未装饰函数(原函数),该函数使装饰器堆叠,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类型注册的,这意味着如果没有找到更好的实现,则使用它。

要检查通用函数为给定类型选择的实现,请使用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 函数。可选的参数是一个元祖,来指定原函数哪些属性被直接分配给装饰器中的匹配属性 ,哪些装饰器属性使用来自原函数的相应属性来更新。The default values for these arguments are the module level constants WRAPPER_ASSIGNMENTS (which assigns to the wrapper function’s __module__, __name__, __qualname__, __annotations__ and __doc__, the documentation string) and WRAPPER_UPDATES (which updates the wrapper function’s __dict__, i.e. 实例的字典).

为了允许访问原始功能以进行内省和其他目的(例如,绕过缓存装饰器(例如lru_cache()),此函数会自动向包装器添加一个__wrapped__属性,该属性引用要包装的函数。

此函数的主要用途是在decorator函数中,它包装修饰的函数并返回包装器。如果不更新包装器函数,返回的函数的元数据将反射包装器的定义,而不是原函数的定义 ,这通常是没有意义的。

update_wrapper()可以与除函数之外的可调用项一起使用。忽略分配的更新中指定的任何属性,这些属性从正在包装的对象中丢失。此函数不会尝试在包装函数中设置它们)。如果包装函数本身缺少在更新的中命名的任何属性,则仍会引发AttributeError

版本3.2中的新功能:自动添加__wrapped__属性。

版本3.2中的新功能:默认情况下复制__annotations__属性。

在版本3.2中更改:缺少的属性不再触发AttributeError

在版本3.4中更改: __wrapped__属性现在总是引用wrapped函数,即使该函数定义了__wrapped__属性。(参见问题17482

@functools.wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)

这是一个方便的函数,用于在定义包装器函数时调用update_wrapper()作为函数装饰器。它等效于partial(update_wrapper, wrapped = wrapped, assigned = assigned, updated = updated) t4 >示例:

>>> 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()的docstring将丢失。

10.2.1. partial对象

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

partial.func

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

partial.args

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

partial.keywords

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

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