# 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和关键字参数keywords对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参数默认为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必须是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参数将被插入作为第一个位置参数，甚至在args和t2>提供给`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])

  将带有两个参数的 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)

  将函数转换为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`对象的行为类似于静态方法，并且在实例属性查找期间不会转换为绑定的方法。