functools.md
1> **Источник:** https://python-all.ru/3/library/functools.html2>3> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.45---67# `functools` – Функции высшего порядка и операции над вызываемыми объектами89**Исходный код:** [Lib/functools.py](https://python-all.ru/src/3.14/Lib/functools.py)1011---1213Модуль `functools` предназначен для функций высшего порядка: функций, которые действуют на или возвращают другие функции. В общем случае любой вызываемый объект может рассматриваться как функция для целей этого модуля.1415Модуль `functools` определяет следующие функции:1617#### `@functools.cache(user_function)`1819Простой легковесный кэш функций без ограничения размера. Иногда называется [«мемоизация»](https://python-all.ru/3/library/functools.html).2021Возвращает то же, что и `lru_cache(maxsize=None)`, создавая тонкую обёртку вокруг поиска по словарю для аргументов функции. Поскольку ему никогда не нужно вытеснять старые значения, он меньше и быстрее, чем [`lru_cache()`](https://python-all.ru/3/library/functools.html#functools.lru_cache) с ограничением размера.2223Например:2425```python26@cache27def factorial(n):28 return n * factorial(n-1) if n else 12930>>> factorial(10) # результат ранее не кэшировался, выполняется 11 рекурсивных вызовов31362880032>>> factorial(5) # нет новых вызовов, просто возвращается кэшированный результат3312034>>> factorial(12) # два новых рекурсивных вызова, factorial(10) кэширован3547900160036```3738Кэш потокобезопасен, поэтому обёрнутую функцию можно использовать в нескольких потоках. Это означает, что базовая структура данных будет оставаться согласованной при конкурентных обновлениях.3940Обёрнутая функция может быть вызвана более одного раза, если другой поток делает дополнительный вызов до того, как первый вызов будет завершён и закэширован.4142Добавлено в версии 3.9.4344#### `@functools.cached_property(func)`4546Преобразует метод класса в свойство, значение которого вычисляется один раз и затем кэшируется как обычный атрибут на всё время существования экземпляра. Аналогично [`property()`](https://python-all.ru/3/library/functions.html#property), но с добавлением кэширования. Полезно для дорогостоящих вычисляемых свойств экземпляров, которые в остальном практически неизменны.4748Пример:4950```python51class DataSet:5253 def __init__(self, sequence_of_numbers):54 self._data = tuple(sequence_of_numbers)5556 @cached_property57 def stdev(self):58 return statistics.stdev(self._data)59```6061Механика `cached_property()` несколько отличается от [`property()`](https://python-all.ru/3/library/functions.html#property). Обычное свойство блокирует запись атрибута, если не определён сеттер. Напротив, *cached\_property* разрешает запись.6263Декоратор *cached\_property* выполняется только при поиске атрибута и только когда атрибут с таким именем не существует. Когда он выполняется, *cached\_property* записывает значение в атрибут с тем же именем. Последующие чтения и записи атрибута имеют приоритет над методом *cached\_property*, и он работает как обычный атрибут.6465Кэшированное значение можно очистить, удалив атрибут. Это позволяет методу *cached\_property* выполниться снова.6667*cached\_property* не предотвращает возможное состояние гонки при многопоточном использовании. Функция геттер может выполниться более одного раза на одном и том же экземпляре, при этом последний запуск устанавливает кэшированное значение. Если кэшированное свойство идемпотентно или его повторное выполнение на экземпляре не вредно, это нормально. Если требуется синхронизация, реализуйте необходимую блокировку внутри декорированной функции геттера или вокруг доступа к кэшированному свойству.6869Примечание: этот декоратор мешает работе [**PEP 412**](https://python-all.ru/3/library/functools.html) словарей с разделением ключей. Это означает, что словари экземпляров могут занимать больше места, чем обычно.7071Кроме того, этот декоратор требует, чтобы атрибут `__dict__` на каждом экземпляре был изменяемым отображением. Это означает, что он не будет работать с некоторыми типами, такими как метаклассы (поскольку атрибуты `__dict__` на экземплярах типа являются прокси только для чтения пространства имён класса), и теми, которые указывают `__slots__` без включения `__dict__` в качестве одного из определённых слотов (так как такие классы вообще не предоставляют атрибут `__dict__`).7273Если изменяемое отображение недоступно или требуется эффективное по памяти разделение ключей, эффекта, аналогичного `cached_property()`, можно также достичь, разместив [`property()`](https://python-all.ru/3/library/functions.html#property) поверх [`lru_cache()`](https://python-all.ru/3/library/functools.html#functools.lru_cache). Смотрите [Как кэшировать вызовы методов?](https://python-all.ru/3/faq/programming.html#faq-cache-method-calls) для получения дополнительных сведений о том, чем это отличается от `cached_property()`.7475Добавлено в версии 3.8.7677Изменено в версии 3.12: До Python 3.12 `cached_property` содержал недокументированную блокировку, чтобы гарантировать, что при многопоточном использовании функция-геттер будет выполняться только один раз на экземпляр. Однако блокировка была по свойству, а не по экземпляру, что могло приводить к неприемлемо высокой конкуренции за блокировку. Начиная с Python 3.12 эта блокировка удалена.7879#### `functools.cmp_to_key(func)`8081Преобразует старую функцию сравнения в [функцию ключа](https://python-all.ru/3/glossary.html#term-key-function). Используется с инструментами, которые принимают функции ключа (такими как [`sorted()`](https://python-all.ru/3/library/functions.html#sorted), [`min()`](https://python-all.ru/3/library/functions.html#min), [`max()`](https://python-all.ru/3/library/functions.html#max), [`heapq.nlargest()`](https://python-all.ru/3/library/heapq.html#heapq.nlargest), [`heapq.nsmallest()`](https://python-all.ru/3/library/heapq.html#heapq.nsmallest), [`itertools.groupby()`](https://python-all.ru/3/library/itertools.html#itertools.groupby)). Эта функция в первую очередь используется как инструмент перехода для программ, переносимых с Python 2, который поддерживал использование функций сравнения.8283Функция сравнения – это любой вызываемый объект, который принимает два аргумента, сравнивает их и возвращает отрицательное число для «меньше», ноль для «равно» или положительное число для «больше». Функция ключа – это вызываемый объект, который принимает один аргумент и возвращает другое значение, используемое как ключ сортировки.8485Пример:8687```python88sorted(iterable, key=cmp_to_key(locale.strcoll)) # порядок сортировки с учётом локали89```9091Примеры сортировки и краткое руководство по сортировке см. в [Методы сортировки](https://python-all.ru/3/howto/sorting.html#sortinghowto).9293Добавлено в версии 3.2.9495#### `@functools.lru_cache(user_function)`9697#### `@functools.lru_cache(maxsize=128, typed=False)`9899Декоратор для обёртывания функции запоминающим вызываемым объектом, который сохраняет до *maxsize* последних вызовов. Это может сэкономить время, когда дорогостоящая или связанная с вводом-выводом функция периодически вызывается с одними и теми же аргументами.100101Кэш потокобезопасен, поэтому обёрнутую функцию можно использовать в нескольких потоках. Это означает, что базовая структура данных будет оставаться согласованной при конкурентных обновлениях.102103Обёрнутая функция может быть вызвана более одного раза, если другой поток делает дополнительный вызов до того, как первый вызов будет завершён и закэширован.104105Поскольку для кэширования результатов используется словарь, позиционные и именованные аргументы функции должны быть [хэшируемыми](https://python-all.ru/3/glossary.html#term-hashable).106107Разные шаблоны аргументов могут считаться отдельными вызовами с отдельными записями в кэше. Например, `f(a=1, b=2)` и `f(b=2, a=1)` отличаются порядком именованных аргументов и могут иметь две отдельные записи в кэше.108109Если указан *user\_function*, он должен быть вызываемым. Это позволяет применить декоратор *lru\_cache* непосредственно к пользовательской функции, оставляя *maxsize* со значением по умолчанию 128:110111```python112@lru_cache113def count_vowels(sentence):114 return sum(sentence.count(vowel) for vowel in 'AEIOUaeiou')115```116117Если *maxsize* установлен в `None`, механизм LRU отключается и кэш может расти без ограничений.118119Если *typed* установлен в true, аргументы функций разных типов будут кэшироваться отдельно. Если *typed* равен false, реализация обычно считает их эквивалентными вызовами и кэширует только один результат. (Некоторые типы, такие как *str* и *int*, могут кэшироваться отдельно, даже если *typed* равен false.)120121Примечание: учёт типа применяется только к непосредственным аргументам функции, а не к их содержимому. Скалярные аргументы `Decimal(42)` и `Fraction(42)` считаются разными вызовами с разными результатами. Напротив, кортежные аргументы `('answer', Decimal(42))` и `('answer', Fraction(42))` считаются эквивалентными.122123Обёрнутая функция оснащается `cache_parameters()` функцией, которая возвращает новый [`dict`](https://python-all.ru/3/library/stdtypes.html#dict), отображающий значения *maxsize* и *typed*. Это только для информационных целей. Изменение значений не влияет на поведение.124125Чтобы оценить эффективность кэша и настроить параметр *maxsize*, обёрнутая функция оснащается `cache_info()` функцией, которая возвращает [именованный кортеж](https://python-all.ru/3/glossary.html#term-named-tuple), показывающий *hits*, *misses*, *maxsize* и *currsize*.126127Декоратор также предоставляет функцию `cache_clear()` для очистки или инвалидации кэша.128129Исходная базовая функция доступна через атрибут `__wrapped__`. Это полезно для интроспекции, обхода кэша или повторной обёртки функции другим кэшем.130131Кэш хранит ссылки на аргументы и возвращаемые значения до тех пор, пока они не устареют и не будут вытеснены из кэша, или пока кэш не будет очищен.132133Если кэшируется метод, аргумент экземпляра `self` также попадает в кэш. См. [Как кэшировать вызовы методов?](https://python-all.ru/3/faq/programming.html#faq-cache-method-calls)134135[LRU-кэш (вытесняющий давно неиспользовавшиеся элементы)](https://python-all.ru/3/library/functools.html) работает лучше всего, когда самые недавние вызовы являются наилучшими предикторами будущих вызовов (например, самые популярные статьи на новостном сервере обычно меняются каждый день). Ограничение размера кэша гарантирует, что кэш не будет расти бесконечно в долго работающих процессах, таких как веб-серверы.136137В целом, LRU-кэш следует использовать только тогда, когда нужно повторно использовать ранее вычисленные значения. Соответственно, не имеет смысла кэшировать функции с побочными эффектами, функции, которые должны создавать различные изменяемые объекты при каждом вызове (например, генераторы и асинхронные функции), или нечистые функции, такие как time() или random().138139Пример LRU-кэша для статического веб-контента:140141```python142@lru_cache(maxsize=32)143def get_pep(num):144 'Retrieve text of a Python Enhancement Proposal'145 resource = f'https://peps.python.org/pep-{num:04d}'146 try:147 with urllib.request.urlopen(resource) as s:148 return s.read()149 except urllib.error.HTTPError:150 return 'Not Found'151152>>> for n in 8, 290, 308, 320, 8, 218, 320, 279, 289, 320, 9991:153... pep = get_pep(n)154... print(n, len(pep))155156>>> get_pep.cache_info()157CacheInfo(hits=3, misses=8, maxsize=32, currsize=8)158```159160Пример эффективного вычисления [чисел Фибоначчи](https://python-all.ru/3/library/functools.html) с использованием кэша для реализации техники [динамического программирования](https://python-all.ru/3/library/functools.html):161162```python163@lru_cache(maxsize=None)164def fib(n):165 if n < 2:166 return n167 return fib(n-1) + fib(n-2)168169>>> [fib(n) for n in range(16)]170[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610]171172>>> fib.cache_info()173CacheInfo(hits=28, misses=16, maxsize=None, currsize=16)174```175176Добавлено в версии 3.2.177178Изменено в версии 3.3: Добавлена опция *typed*.179180Изменено в версии 3.8: Добавлена опция *user\_function*.181182Изменено в версии 3.9: Добавлена функция `cache_parameters()`183184#### `@functools.total_ordering`185186Для класса, определяющего один или несколько методов расширенного сравнения, этот декоратор класса предоставляет остальные. Это упрощает задачу определения всех возможных операций расширенного сравнения:187188Класс должен определять один из [`__lt__()`](https://python-all.ru/3/reference/datamodel.html#object.__lt__), [`__le__()`](https://python-all.ru/3/reference/datamodel.html#object.__le__), [`__gt__()`](https://python-all.ru/3/reference/datamodel.html#object.__gt__) или [`__ge__()`](https://python-all.ru/3/reference/datamodel.html#object.__ge__). Кроме того, класс должен предоставлять метод [`__eq__()`](https://python-all.ru/3/reference/datamodel.html#object.__eq__).189190Например:191192```python193@total_ordering194class Student:195 def _is_valid_operand(self, other):196 return (hasattr(other, "lastname") and197 hasattr(other, "firstname"))198 def __eq__(self, other):199 if not self._is_valid_operand(other):200 return NotImplemented201 return ((self.lastname.lower(), self.firstname.lower()) ==202 (other.lastname.lower(), other.firstname.lower()))203 def __lt__(self, other):204 if not self._is_valid_operand(other):205 return NotImplemented206 return ((self.lastname.lower(), self.firstname.lower()) <207 (other.lastname.lower(), other.firstname.lower()))208```209210> **Примечание**211>212> Хотя этот декоратор упрощает создание хорошо ведущих себя полностью упорядоченных типов, он *действительно* приводит к более медленному выполнению и более сложным трассировкам стека для производных методов сравнения. Если бенчмаркинг производительности показывает, что это является узким местом для конкретного приложения, реализация всех шести методов расширенного сравнения вместо этого, вероятно, даст лёгкий прирост скорости.213214> **Примечание**215>216> Этот декоратор не пытается переопределить методы, объявленные в классе *или его суперклассах*. Это означает, что если суперкласс определяет оператор сравнения, *total\_ordering* не будет реализовывать его снова, даже если исходный метод является абстрактным.217218Добавлено в версии 3.2.219220Изменено в версии 3.4: Теперь поддерживается возврат `NotImplemented` из базовой функции сравнения для нераспознанных типов.221222#### `functools.Placeholder`223224Объект-одиночка, используемый в качестве стража для резервирования места для позиционных аргументов при вызове [`partial()`](https://python-all.ru/3/library/functools.html#functools.partial) и [`partialmethod()`](https://python-all.ru/3/library/functools.html#functools.partialmethod).225226Добавлено в версии 3.14.227228#### `functools.partial(func, /, *args, **keywords)`229230Возвращает новый [объект partial](https://python-all.ru/3/library/functools.html#partial-objects), который при вызове будет вести себя как *func*, вызванная с позиционными аргументами *args* и именованными аргументами *keywords*. Если при вызове передаётся больше аргументов, они добавляются к *args*. Если передаются дополнительные именованные аргументы, они расширяют и переопределяют *keywords*. Примерно эквивалентно следующему:231232```python233def partial(func, /, *args, **keywords):234 def newfunc(*more_args, **more_keywords):235 return func(*args, *more_args, **(keywords | more_keywords))236 newfunc.func = func237 newfunc.args = args238 newfunc.keywords = keywords239 return newfunc240```241242Функция `partial()` используется для частичного применения функции, которое «замораживает» некоторую часть аргументов и/или именованных аргументов функции, создавая новый объект с упрощённой сигнатурой. Например, `partial()` можно использовать для создания вызываемого объекта, который ведёт себя как функция [`int()`](https://python-all.ru/3/library/functions.html#int), у которой аргумент *base* по умолчанию равен `2`:243244```pycon245>>> basetwo = partial(int, base=2)246>>> basetwo.__doc__ = 'Convert base 2 string to an int.'247>>> basetwo('10010')24818249```250251Если в *args* присутствуют сторожевые значения [`Placeholder`](https://python-all.ru/3/library/functools.html#functools.Placeholder), они будут заполнены в первую очередь при вызове `partial()`. Это позволяет предварительно заполнить любой позиционный аргумент с помощью вызова `partial()`; без `Placeholder` можно предварительно заполнить только выбранное количество начальных позиционных аргументов.252253Если присутствуют какие-либо сторожевые значения `Placeholder`, все они должны быть заполнены во время вызова:254255```pycon256>>> say_to_world = partial(print, Placeholder, Placeholder, "world!")257>>> say_to_world('Hello', 'dear')258Hello dear world!259```260261Вызов `say_to_world('Hello')` вызывает [`TypeError`](https://python-all.ru/3/library/exceptions.html#TypeError), потому что предоставлен только один позиционный аргумент, но есть два заполнителя, которые необходимо заполнить.262263Если `partial()` применяется к существующему объекту `partial()`, сторожевые значения `Placeholder` входного объекта заполняются новыми позиционными аргументами. Заполнитель можно сохранить, вставив новый сторож `Placeholder` на место, занимаемое предыдущим `Placeholder`:264265```pycon266>>> from functools import partial, Placeholder as _267>>> remove = partial(str.replace, _, _, '')268>>> message = 'Hello, dear dear world!'269>>> remove(message, ' dear')270'Hello, world!'271>>> remove_dear = partial(remove, _, ' dear')272>>> remove_dear(message)273'Hello, world!'274>>> remove_first_dear = partial(remove_dear, _, 1)275>>> remove_first_dear(message)276'Hello, dear world!'277```278279`Placeholder` нельзя передать в `partial()` как именованный аргумент.280281Изменено в версии 3.14: Добавлена поддержка [`Placeholder`](https://python-all.ru/3/library/functools.html#functools.Placeholder) в позиционных аргументах.282283#### `class functools.partialmethod(func, /, *args, **keywords)`284285Возвращает новый дескриптор `partialmethod`, который ведёт себя как [`partial`](https://python-all.ru/3/library/functools.html#functools.partial), за исключением того, что он предназначен для использования в качестве определения метода, а не для прямого вызова.286287*func* должен быть [дескриптором](https://python-all.ru/3/glossary.html#term-descriptor) или вызываемым объектом (объекты, являющиеся и тем и другим, как обычные функции, обрабатываются как дескрипторы).288289Когда *func* является дескриптором (например, обычная функция Python, [`classmethod()`](https://python-all.ru/3/library/functions.html#classmethod), [`staticmethod()`](https://python-all.ru/3/library/functions.html#staticmethod), [`abstractmethod()`](https://python-all.ru/3/library/abc.html#abc.abstractmethod) или другой экземпляр `partialmethod`), вызовы `__get__` делегируются базовому дескриптору, а в качестве результата возвращается подходящий [частичный объект](https://python-all.ru/3/library/functools.html#partial-objects).290291Когда *func* – это вызываемый объект, не являющийся дескриптором, динамически создаётся соответствующий связанный метод. Он ведёт себя как обычная функция Python при использовании в качестве метода: аргумент *self* будет вставлен как первый позиционный аргумент, даже до *args* и *keywords*, переданных конструктору `partialmethod`.292293Пример:294295```python296>>> class Cell:297... def __init__(self):298... self._alive = False299... @property300... def alive(self):301... return self._alive302... def set_state(self, state):303... self._alive = bool(state)304... set_alive = partialmethod(set_state, True)305... set_dead = partialmethod(set_state, False)306...307>>> c = Cell()308>>> c.alive309False310>>> c.set_alive()311>>> c.alive312True313```314315Добавлено в версии 3.4.316317#### `functools.reduce(function, iterable, /[, initial])`318319Применяет *function* от двух аргументов кумулятивно к элементам *iterable* слева направо, чтобы свести итерабельный объект к одному значению. Например, `reduce(lambda x, y: x+y, [1, 2, 3, 4, 5])` вычисляет `((((1+2)+3)+4)+5)`. Левый аргумент *x* – это накопленное значение, а правый аргумент *y* – это обновляемое значение из *iterable*. Если указан необязательный *initial*, он помещается перед элементами итерабельного объекта в вычислениях и служит значением по умолчанию, когда итерабельный объект пуст. Если *initial* не задан, а *iterable* содержит только один элемент, возвращается первый элемент.320321Примерно эквивалентно:322323```python324initial_missing = object()325326def reduce(function, iterable, /, initial=initial_missing):327 it = iter(iterable)328 if initial is initial_missing:329 value = next(it)330 else:331 value = initial332 for element in it:333 value = function(value, element)334 return value335```336337См. [`itertools.accumulate()`](https://python-all.ru/3/library/itertools.html#itertools.accumulate) для итератора, который возвращает все промежуточные значения.338339Изменено в версии 3.14: *initial* теперь поддерживается как именованный аргумент.340341#### `@functools.singledispatch`342343Преобразует функцию в [однодиспетчерскую](https://python-all.ru/3/glossary.html#term-single-dispatch) [обобщённую функцию](https://python-all.ru/3/glossary.html#term-generic-function).344345Чтобы определить обобщённую функцию, декорируйте её с помощью декоратора `@singledispatch`. При определении функции с помощью `@singledispatch` учтите, что диспетчеризация происходит по типу первого аргумента:346347```python348>>> from functools import singledispatch349>>> @singledispatch350... def fun(arg, verbose=False):351... if verbose:352... print("Let me just say,", end=" ")353... print(arg)354```355356Чтобы добавить перегруженные реализации функции, используйте атрибут `register()` обобщённой функции, который можно использовать как декоратор. Для функций с аннотациями типов декоратор автоматически определяет тип первого аргумента:357358```python359>>> @fun.register360... def _(arg: int, verbose=False):361... if verbose:362... print("Strength in numbers, eh?", end=" ")363... print(arg)364...365>>> @fun.register366... def _(arg: list, verbose=False):367... if verbose:368... print("Enumerate this:")369... for i, elem in enumerate(arg):370... print(i, elem)371```372373[`typing.Union`](https://python-all.ru/3/library/typing.html#typing.Union) также можно использовать:374375```python376>>> @fun.register377... def _(arg: int | float, verbose=False):378... if verbose:379... print("Strength in numbers, eh?", end=" ")380... print(arg)381...382>>> from typing import Union383>>> @fun.register384... def _(arg: Union[list, set], verbose=False):385... if verbose:386... print("Enumerate this:")387... for i, elem in enumerate(arg):388... print(i, elem)389...390```391392Для кода, не использующего аннотации типов, подходящий тип можно передать явно самому декоратору:393394```python395>>> @fun.register(complex)396... def _(arg, verbose=False):397... if verbose:398... print("Better than complicated.", end=" ")399... print(arg.real, arg.imag)400...401```402403Для кода, который выполняет диспетчеризацию по типу коллекции (например, `list`), но хочет аннотировать типы элементов коллекции (например, `list[int]`), тип для диспетчеризации следует явно передать самому декоратору, а аннотация типа указывается в определении функции:404405```python406>>> @fun.register(list)407... def _(arg: list[int], verbose=False):408... if verbose:409... print("Enumerate this:")410... for i, elem in enumerate(arg):411... print(i, elem)412```413414> **Примечание**415>416> Во время выполнения функция будет выполнять диспетчеризацию по экземпляру списка независимо от типа элементов внутри списка, то есть `[1,2,3]` будет диспетчеризоваться так же, как `["foo", "bar", "baz"]`. Аннотация, приведённая в этом примере, предназначена только для статических проверок типов и не влияет на выполнение.417418Чтобы можно было регистрировать [лямбды](https://python-all.ru/3/glossary.html#term-lambda) и уже существующие функции, атрибут [`register()`](https://python-all.ru/3/library/functools.html#functools.singledispatch.register) также можно использовать в функциональной форме:419420```python421>>> def nothing(arg, verbose=False):422... print("Nothing.")423...424>>> fun.register(type(None), nothing)425```426427Атрибут [`register()`](https://python-all.ru/3/library/functools.html#functools.singledispatch.register) возвращает недекорированную функцию. Это позволяет накладывать декораторы, [`pickling`](https://python-all.ru/3/library/pickle.html#module-pickle) и создавать модульные тесты для каждого варианта независимо:428429```python430>>> @fun.register(float)431... @fun.register(Decimal)432... def fun_num(arg, verbose=False):433... if verbose:434... print("Half of your number:", end=" ")435... print(arg / 2)436...437>>> fun_num is fun438False439```440441При вызове обобщённая функция выполняет диспетчеризацию по типу первого аргумента:442443```python444>>> fun("Hello, world.")445Hello, world.446>>> fun("test.", verbose=True)447Let me just say, test.448>>> fun(42, verbose=True)449Strength in numbers, eh? 42450>>> fun(['spam', 'spam', 'eggs', 'spam'], verbose=True)451Enumerate this:4520 spam4531 spam4542 eggs4553 spam456>>> fun(None)457Nothing.458>>> fun(1.23)4590.615460```461462Если для конкретного типа нет зарегистрированной реализации, используется порядок разрешения методов (MRO) этого типа для поиска более общей реализации. Исходная функция, декорированная `@singledispatch`, зарегистрирована для базового типа [`object`](https://python-all.ru/3/library/functions.html#object), то есть она используется, если не найдено более подходящей реализации.463464Если реализация зарегистрирована для [абстрактного базового класса](https://python-all.ru/3/glossary.html#term-abstract-base-class), виртуальные подклассы этого базового класса будут диспетчеризоваться к этой реализации:465466```python467>>> from collections.abc import Mapping468>>> @fun.register469... def _(arg: Mapping, verbose=False):470... if verbose:471... print("Keys & Values")472... for key, value in arg.items():473... print(key, "=>", value)474...475>>> fun({"a": "b"})476a => b477```478479Чтобы проверить, какую реализацию выберет обобщённая функция для заданного типа, используйте атрибут `dispatch()`:480481```python482>>> fun.dispatch(float)483<function fun_num at 0x1035a2840>484>>> fun.dispatch(dict) # примечание: реализация по умолчанию485<function fun at 0x103fe0000>486```487488Чтобы получить доступ ко всем зарегистрированным реализациям, используйте атрибут `registry` только для чтения:489490```python491>>> fun.registry.keys()492dict_keys([<class 'NoneType'>, <class 'int'>, <class 'object'>,493 <class 'decimal.Decimal'>, <class 'list'>,494 <class 'float'>])495>>> fun.registry[float]496<function fun_num at 0x1035a2840>497>>> fun.registry[object]498<function fun at 0x103fe0000>499```500501Добавлено в версии 3.4.502503Изменено в версии 3.7: Атрибут [`register()`](https://python-all.ru/3/library/functools.html#functools.singledispatch.register) теперь поддерживает использование аннотаций типов.504505Изменено в версии 3.11: Атрибут [`register()`](https://python-all.ru/3/library/functools.html#functools.singledispatch.register) теперь поддерживает [`typing.Union`](https://python-all.ru/3/library/typing.html#typing.Union) в качестве аннотации типа.506507#### `class functools.singledispatchmethod(func)`508509Преобразует метод в [однодиспетчерскую](https://python-all.ru/3/glossary.html#term-single-dispatch) [обобщённую функцию](https://python-all.ru/3/glossary.html#term-generic-function).510511Чтобы определить обобщённый метод, декорируйте его с помощью декоратора `@singledispatchmethod`. При определении метода с помощью `@singledispatchmethod` учтите, что диспетчеризация происходит по типу первого аргумента, не являющегося *self* или *cls*:512513```python514class Negator:515 @singledispatchmethod516 def neg(self, arg):517 raise NotImplementedError("Cannot negate a")518519 @neg.register520 def _(self, arg: int):521 return -arg522523 @neg.register524 def _(self, arg: bool):525 return not arg526```527528`@singledispatchmethod` поддерживает вложение с другими декораторами, такими как [`@classmethod`](https://python-all.ru/3/library/functions.html#classmethod). Обратите внимание, что для поддержки `dispatcher.register` `singledispatchmethod` должен быть *самым внешним* декоратором. Вот класс `Negator` с методами `neg`, привязанными к классу, а не к экземпляру класса:529530```python531class Negator:532 @singledispatchmethod533 @classmethod534 def neg(cls, arg):535 raise NotImplementedError("Cannot negate a")536537 @neg.register538 @classmethod539 def _(cls, arg: int):540 return -arg541542 @neg.register543 @classmethod544 def _(cls, arg: bool):545 return not arg546```547548Тот же шаблон можно использовать для других подобных декораторов: [`@staticmethod`](https://python-all.ru/3/library/functions.html#staticmethod), [`@~abc.abstractmethod`](https://python-all.ru/3/library/abc.html#abc.abstractmethod) и других.549550Добавлено в версии 3.8.551552#### `functools.update_wrapper(wrapper, wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)`553554Обновляет функцию-*обёртку* так, чтобы она выглядела как *обёрнутую* функцию. Необязательные аргументы – кортежи, определяющие, какие атрибуты исходной функции напрямую присваиваются соответствующим атрибутам функции-обёртки, а какие атрибуты функции-обёртки обновляются соответствующими атрибутами исходной функции. Значения по умолчанию для этих аргументов – константы уровня модуля `WRAPPER_ASSIGNMENTS` (которая присваивает функции-обёртке атрибуты [`__module__`](https://python-all.ru/3/reference/datamodel.html#function.__module__), [`__name__`](https://python-all.ru/3/reference/datamodel.html#function.__name__), [`__qualname__`](https://python-all.ru/3/reference/datamodel.html#function.__qualname__), [`__annotations__`](https://python-all.ru/3/reference/datamodel.html#function.__annotations__), [`__type_params__`](https://python-all.ru/3/reference/datamodel.html#function.__type_params__) и [`__doc__`](https://python-all.ru/3/reference/datamodel.html#function.__doc__) – строку документации) и `WRAPPER_UPDATES` (которая обновляет [`__dict__`](https://python-all.ru/3/reference/datamodel.html#function.__dict__) функции-обёртки, т.е. словарь экземпляра).555556Чтобы обеспечить доступ к исходной функции для интроспекции и других целей (например, обхода кэширующего декоратора такого как [`lru_cache()`](https://python-all.ru/3/library/functools.html#functools.lru_cache)), эта функция автоматически добавляет атрибут `__wrapped__` к обёртке, который ссылается на обёрнутую функцию.557558Основное назначение этой функции – использование в [decorator](https://python-all.ru/3/glossary.html#term-decorator)-функциях, которые оборачивают декорированную функцию и возвращают обёртку. Если функция-обёртка не обновляется, метаданные возвращаемой функции будут отражать определение обёртки, а не исходной функции, что обычно не очень полезно.559560`update_wrapper()` можно использовать с вызываемыми объектами, отличными от функций. Любые атрибуты, указанные в *assigned* или *updated*, отсутствующие в оборачиваемом объекте, игнорируются (т.е. эта функция не будет пытаться установить их в функции-обёртке). [`AttributeError`](https://python-all.ru/3/library/exceptions.html#AttributeError) по-прежнему возбуждается, если сама функция-обёртка не имеет каких-либо атрибутов, перечисленных в *updated*.561562Изменено в версии 3.2: Атрибут `__wrapped__` теперь добавляется автоматически. Атрибут [`__annotations__`](https://python-all.ru/3/reference/datamodel.html#function.__annotations__) теперь копируется по умолчанию. Отсутствующие атрибуты больше не вызывают [`AttributeError`](https://python-all.ru/3/library/exceptions.html#AttributeError).563564Изменено в версии 3.4: Атрибут `__wrapped__` теперь всегда ссылается на обёрнутую функцию, даже если эта функция определяла атрибут `__wrapped__`. (см. [bpo-17482](https://python-all.ru/3/library/functools.html))565566Изменено в версии 3.12: Атрибут [`__type_params__`](https://python-all.ru/3/reference/datamodel.html#function.__type_params__) теперь копируется по умолчанию.567568#### `@functools.wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)`569570Это удобная функция для вызова [`update_wrapper()`](https://python-all.ru/3/library/functools.html#functools.update_wrapper) в качестве декоратора функции при определении функции-обёртки. Она эквивалентна `partial(update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated)`. Например:571572```python573>>> from functools import wraps574>>> def my_decorator(f):575... @wraps(f)576... def wrapper(*args, **kwds):577... print('Calling decorated function')578... return f(*args, **kwds)579... return wrapper580...581>>> @my_decorator582... def example():583... """Docstring"""584... print('Called example function')585...586>>> example()587Calling decorated function588Called example function589>>> example.__name__590'example'591>>> example.__doc__592'Docstring'593```594595Без использования этой фабрики декораторов имя примера функции было бы `'wrapper'`, а строка документации исходной `example()` была бы потеряна.596597## [`partial`](https://python-all.ru/3/library/functools.html#functools.partial) Объекты598599Объекты [`partial`](https://python-all.ru/3/library/functools.html#functools.partial) – это вызываемые объекты, создаваемые [`partial()`](https://python-all.ru/3/library/functools.html#functools.partial). Они имеют три атрибута только для чтения:600601#### `partial.func`602603Вызываемый объект или функция. Вызовы объекта [`partial`](https://python-all.ru/3/library/functools.html#functools.partial) будут перенаправлены в [`func`](https://python-all.ru/3/library/functools.html#functools.partial.func) с новыми аргументами и ключевыми словами.604605#### `partial.args`606607Крайние левые позиционные аргументы, которые будут добавлены перед позиционными аргументами, переданными при вызове объекта [`partial`](https://python-all.ru/3/library/functools.html#functools.partial).608609#### `partial.keywords`610611Ключевые аргументы, которые будут предоставлены при вызове объекта [`partial`](https://python-all.ru/3/library/functools.html#functools.partial).612613Объекты [`partial`](https://python-all.ru/3/library/functools.html#functools.partial) похожи на [объекты функций](https://python-all.ru/3/reference/datamodel.html#user-defined-funcs) тем, что они вызываемы, поддерживают слабые ссылки и могут иметь атрибуты. Есть несколько важных отличий. Например, атрибуты [`__name__`](https://python-all.ru/3/library/stdtypes.html#definition.__name__) и [`__doc__`](https://python-all.ru/3/library/stdtypes.html#definition.__doc__) не создаются автоматически.614