# `functools` — Higher-order functions and operations on callable objects

Вихідний код: Lib/functools.py

Модуль `functools` призначений для функцій вищого порядку: функцій, які діють або повертають інші функції. Загалом, будь-який викликуваний об’єкт можна розглядати як функцію для цілей цього модуля.

Модуль `functools` визначає такі функції:

@functools.cache(user_function)

  Простий легкий необмежений кеш функцій. Іноді називається «memoize».

  Returns the same as `lru_cache(maxsize=None)`, creating a thin wrapper around a dictionary lookup for the function arguments. Because it never needs to evict old values, this is smaller and faster than `lru_cache()` with a size limit.

  Наприклад:

      @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

  The cache is threadsafe so that the wrapped function can be used in multiple threads. This means that the underlying data structure will remain coherent during concurrent updates.

  It is possible for the wrapped function to be called more than once if another thread makes an additional call before the initial call has been completed and cached.

  Нове в версії 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()`. Звичайна властивість блокує запис атрибута, якщо не визначено установщик. На відміну від цього, cached_property дозволяє запис.

  Декоратор cached_property працює лише під час пошуку й лише тоді, коли атрибут із такою ж назвою не існує. Коли він виконується, cached_property записує в атрибут з таким же ім’ям. Наступні атрибути читання та запису мають пріоритет над методом cached_property, і він працює як звичайний атрибут.

  Кешоване значення можна очистити, видаливши атрибут. Це дозволяє знову запустити метод cached_property.

  Зауважте, що цей декоратор заважає роботі словників спільного ключа PEP 412. Це означає, що словники примірників можуть займати більше місця, ніж зазвичай.

  Крім того, цей декоратор вимагає, щоб атрибут `__dict__` для кожного екземпляра був змінним відображенням. Це означає, що він не працюватиме з деякими типами, такими як метакласи (оскільки атрибути `__dict__` в екземплярах типу є проксі-серверами лише для читання для простору імен класу), і ті, які вказують `__slots__` без включення `__dict__` як один із визначених слотів (оскільки такі класи взагалі не надають атрибут `__dict__`).

  If a mutable mapping is not available or if space-efficient key sharing is desired, an effect similar to `cached_property()` can also be achieved by stacking `property()` on top of `lru_cache()`. See Як кешувати виклики методів? for more details on how this differs from `cached_property()`.

  Нове в версії 3.8.

functools.cmp_to_key(func)

  Перетворіть функцію порівняння старого стилю на key function. Використовується з інструментами, які приймають ключові функції (такі як `sorted()`, `min()`, `max()`, `heapq.nlargest()`, `heapq.nsmallest()`, `itertools.groupby()`). Ця функція в основному використовується як інструмент переходу для програм, які перетворюються з Python 2, який підтримує використання функцій порівняння.

  A comparison function is any callable that accepts two arguments, compares them, and returns a negative number for less-than, zero for equality, or a positive number for greater-than. A key function is a callable that accepts one argument and returns another value to be used as the sort key.

  Приклад:

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

  Приклади сортування та короткий посібник із сортування див. Sorting HOW TO.

  Нове в версії 3.2.

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

  Декоратор для обгортання функції викликом мемоізації, який зберігає до maxsize останніх викликів. Це може заощадити час, коли дорога функція або функція, пов’язана з вводом/виводом, періодично викликається з однаковими аргументами.

  The cache is threadsafe so that the wrapped function can be used in multiple threads. This means that the underlying data structure will remain coherent during concurrent updates.

  It is possible for the wrapped function to be called more than once if another thread makes an additional call before the initial call has been completed and cached.

  Since a dictionary is used to cache results, the positional and keyword arguments to the function must be hashable.

  Distinct argument patterns may be considered to be distinct calls with separate cache entries. For example, `f(a=1, b=2)` and `f(b=2, a=1)` differ in their keyword argument order and may have two separate cache entries.

  Якщо вказано 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, реалізація зазвичай розглядатиме їх як еквівалентні виклики та кешуватиме лише один результат. (Деякі типи, такі як str і int, можуть кешуватися окремо, навіть якщо typed має значення false.)

  Зауважте, що специфічність типу застосовується лише до безпосередніх аргументів функції, а не до їх вмісту. Скалярні аргументи `Decimal(42)` і `Fraction(42)` розглядаються як окремі виклики з різними результатами. Навпаки, аргументи кортежу `('answer', Decimal(42))` і `('answer', Fraction(42))` розглядаються як еквівалентні.

  The wrapped function is instrumented with a `cache_parameters()` function that returns a new `dict` showing the values for maxsize and typed. This is for information purposes only. Mutating the values has no effect.

  Щоб допомогти виміряти ефективність кешу та налаштувати параметр maxsize, обгорнута функція обладнана функцією `cache_info()`, яка повертає named tuple, що показує влучення, промахи, maxsize і currsize.

  Декоратор також надає функцію `cache_clear()` для очищення або анулювання кешу.

  Оригінальна базова функція доступна через атрибут `__wrapped__`. Це корисно для самоаналізу, для обходу кешу або для перезагортання функції в інший кеш.

  Кеш зберігає посилання на аргументи та значення, що повертаються, доки вони не вичерпаються з кешу або поки кеш не буде очищено.

  If a method is cached, the `self` instance argument is included in the cache. See Як кешувати виклики методів?

  An LRU (least recently used) cache works best when the most recent calls are the best predictors of upcoming calls (for example, the most popular articles on a news server tend to change each day). The cache’s size limit assures that the cache does not grow without bound on long-running processes such as web servers.

  In general, the LRU cache should only be used when you want to reuse previously computed values. Accordingly, it doesn’t make sense to cache functions with side-effects, functions that need to create distinct mutable objects on each call (such as generators and async functions), or impure functions such as time() or random().

  Приклад кешу LRU для статичного веб-контенту:

      @lru_cache(maxsize=32)
      def get_pep(num):
          'Retrieve text of a Python Enhancement Proposal'
          resource = 'https://peps.python.org/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: Додано параметр введений.

  Змінено в версії 3.8: Додано опцію user_function.

  Змінено в версії 3.9: Added the function `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: Returning NotImplemented from the underlying comparison function for unrecognised types is now supported.

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

  Повертає новий частковий об’єкт, який під час виклику поводитиметься як func, що викликається з позиційними аргументами args і ключовими аргументами keywords. Якщо до виклику надається більше аргументів, вони додаються до args. Якщо надаються додаткові ключові аргументи, вони розширюють і замінюють ключові слова. Приблизно еквівалентно:

      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 за замовчуванням дорівнює двом:

      >>> 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__` делегуються базовому дескриптору, а в результаті повертається відповідний частковий об’єкт.

  Коли func є недескрипторним викликом, відповідний пов’язаний метод створюється динамічно. Це поводиться як звичайна функція Python, коли використовується як метод: аргумент self буде вставлено як перший позиційний аргумент, навіть перед args і keywords, наданими конструктору `partialmethod`.

  Приклад:

      >>> 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[, initializer])

  Apply function of two arguments cumulatively to the items of iterable, from left to right, so as to reduce the iterable to a single value. For example, `reduce(lambda x, y: x+y, [1, 2, 3, 4, 5])` calculates `((((1+2)+3)+4)+5)`. The left argument, x, is the accumulated value and the right argument, y, is the update value from the iterable. If the optional initializer is present, it is placed before the items of the iterable in the calculation, and serves as a default when the iterable is empty. If initializer is not given and iterable contains only one item, the first item is returned.

  Приблизно еквівалентно:

      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

  Перегляньте `itertools.accumulate()` для ітератора, який видає всі проміжні значення.

@functools.singledispatch

  Перетворення функції на single-dispatch generic function.

  Щоб визначити загальну функцію, прикрасьте її за допомогою декоратора `@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` and `typing.Union` can also be used:

      >>> @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)
      ...

  Щоб увімкнути реєстрацію 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`, зареєстрована для базового типу `object`, що означає, що вона використовується, якщо не знайдено кращої реалізації.

  Якщо реалізацію зареєстровано в abstract base class, віртуальні підкласи базового класу будуть відправлені до цієї реалізації:

      >>> 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: The `register()` attribute now supports `types.UnionType` and `typing.Union` as type annotations.

class functools.singledispatchmethod(func)

  Перетворення методу на single-dispatch generic function.

  Щоб визначити загальний метод, прикрасьте його декоратором `@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)

  Update a wrapper function to look like the wrapped function. The optional arguments are tuples to specify which attributes of the original function are assigned directly to the matching attributes on the wrapper function and which attributes of the wrapper function are updated with the corresponding attributes from the original function. 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. the instance dictionary).

  Щоб дозволити доступ до оригінальної функції для самоаналізу та інших цілей (наприклад, обхід декоратора кешування, такого як `lru_cache()`), ця функція автоматично додає атрибут `__wrapped__` до оболонки, яка посилається на функцію, яку обгортають.

  Основним призначенням цієї функції є функції decorator, які обертають декоровану функцію та повертають оболонку. Якщо функція-оболонка не оновлена, метадані повернутої функції відображатимуть визначення оболонки, а не початкове визначення функції, що зазвичай не дуже корисно.

  `update_wrapper()` можна використовувати з іншими викликами, ніж функції. Будь-які атрибути з іменами assigned або updated, яких немає в об’єкті, що обгортається, ігноруються (тобто ця функція не намагатиметься встановити їх у функції обгортки). `AttributeError` все ще виникає, якщо в самій функції-обгортки відсутні будь-які атрибути, названі в updated.

  Змінено в версії 3.2: The `__wrapped__` attribute is now automatically added. The `__annotations__` attribute is now copied by default. Missing attributes no longer trigger an `AttributeError`.

  Змінено в версії 3.4: Атрибут `__wrapped__` тепер завжди посилається на обгорнуту функцію, навіть якщо ця функція визначила атрибут `__wrapped__`. (див. bpo-17482)

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

  Це зручна функція для виклику `update_wrapper()` як декоратора функції під час визначення функції-огортки. Це еквівалентно `partial(update_wrapper, wrapped=wrapped, 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.func

  Викликаний об’єкт або функція. Виклики об’єкта `partial` будуть перенаправлені до `func` з новими аргументами та ключовими словами.

partial.args

  Крайні ліві позиційні аргументи, які будуть додані до позиційних аргументів, наданих до виклику об’єкта `partial`.

partial.keywords

  Ключові аргументи, які будуть надані під час виклику об’єкта `partial`.

`partial` objects are like `function` objects in that they are callable, weak referenceable, and can have attributes. There are some important differences. For instance, the `__name__` and `__doc__` attributes are not created automatically. Also, `partial` objects defined in classes behave like static methods and do not transform into bound methods during instance attribute look-up.