Документация Python неофициальный перевод

functools.md

618 строк · 49.3 КБ · обычная страница · сырой текст · скачать

1> **Источник:** https://python-all.ru/3.16/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/main/Lib/functools.py)1011---1213Модуль `functools` предназначен для функций высшего порядка: функций, которые действуют на или возвращают другие функции. В общем случае любой вызываемый объект может рассматриваться как функция для целей этого модуля.1415Модуль `functools` определяет следующие функции:1617#### `@functools.cache(user_function)`1819Простой легковесный кэш функций без ограничения размера. Иногда называется [«мемоизация»](https://python-all.ru/3.16/library/functools.html).2021Возвращает то же, что и `lru_cache(maxsize=None)`, создавая тонкую обёртку вокруг поиска по словарю для аргументов функции. Поскольку ему никогда не нужно вытеснять старые значения, он меньше и быстрее, чем [`@lru_cache`](https://python-all.ru/3.16/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Поведение «вызов один раз» не гарантируется, так как блокировки не удерживаются во время вызова функции. Потенциально может произойти ещё один вызов с теми же аргументами, пока первый вызов ещё выполняется.4344Добавлено в версии 3.9.4546#### `@functools.cached_property(func)`4748Преобразует метод класса в свойство, значение которого вычисляется один раз и затем кэшируется как обычный атрибут на всё время существования экземпляра. Аналогично [`@property`](https://python-all.ru/3.16/library/functions.html#property), но с добавлением кэширования. Полезно для дорогостоящих вычисляемых свойств экземпляров, которые в остальном практически неизменны.4950Пример:5152```python53class DataSet:5455    def __init__(self, sequence_of_numbers):56        self._data = tuple(sequence_of_numbers)5758    @cached_property59    def stdev(self):60        return statistics.stdev(self._data)61```6263Механика [`@cached_property`](https://python-all.ru/3.16/library/functools.html#functools.cached_property) несколько отличается от [`@property`](https://python-all.ru/3.16/library/functions.html#property). Обычное свойство блокирует запись атрибута, если не определён сеттер. Напротив, *cached\_property* разрешает запись.6465Декоратор *cached\_property* выполняется только при поиске атрибута и только когда атрибут с таким именем не существует. Когда он выполняется, *cached\_property* записывает значение в атрибут с тем же именем. Последующие чтения и записи атрибута имеют приоритет над методом *cached\_property*, и он работает как обычный атрибут.6667Кэшированное значение можно очистить, удалив атрибут. Это позволяет методу *cached\_property* выполниться снова.6869*cached\_property* не предотвращает возможное состояние гонки при многопоточном использовании. Функция геттер может выполниться более одного раза на одном и том же экземпляре, при этом последний запуск устанавливает кэшированное значение. Если кэшированное свойство идемпотентно или его повторное выполнение на экземпляре не вредно, это нормально. Если требуется синхронизация, реализуйте необходимую блокировку внутри декорированной функции геттера или вокруг доступа к кэшированному свойству.7071Примечание: этот декоратор мешает работе [**PEP 412**](https://python-all.ru/3.16/library/functools.html) словарей с разделением ключей. Это означает, что словари экземпляров могут занимать больше места, чем обычно.7273Кроме того, этот декоратор требует, чтобы атрибут `__dict__` на каждом экземпляре был изменяемым отображением. Это означает, что он не будет работать с некоторыми типами, такими как метаклассы (поскольку атрибуты `__dict__` на экземплярах типа являются прокси только для чтения пространства имён класса), и теми, которые указывают `__slots__` без включения `__dict__` в качестве одного из определённых слотов (так как такие классы вообще не предоставляют атрибут `__dict__`).7475Если изменяемое отображение недоступно или требуется эффективное по памяти разделение ключей, эффекта, аналогичного [`@cached_property`](https://python-all.ru/3.16/library/functools.html#functools.cached_property), можно также достичь, разместив [`@property`](https://python-all.ru/3.16/library/functions.html#property) поверх [`@lru_cache`](https://python-all.ru/3.16/library/functools.html#functools.lru_cache). Смотрите [Как кэшировать вызовы методов?](https://python-all.ru/3.16/faq/programming.html#faq-cache-method-calls) для получения дополнительных сведений о том, чем это отличается от `@cached_property`.7677Добавлено в версии 3.8.7879Изменено в версии 3.12: До Python 3.12 `@cached_property` содержал недокументированную блокировку, чтобы гарантировать, что при многопоточном использовании функция-геттер будет выполняться только один раз на экземпляр. Однако блокировка была по свойству, а не по экземпляру, что могло приводить к неприемлемо высокой конкуренции за блокировку. Начиная с Python 3.12 эта блокировка удалена.8081#### `functools.cmp_to_key(func)`8283Преобразует старую функцию сравнения в [функцию ключа](https://python-all.ru/3.16/glossary.html#term-key-function). Используется с инструментами, которые принимают функции ключа (такими как [`sorted()`](https://python-all.ru/3.16/library/functions.html#sorted), [`min()`](https://python-all.ru/3.16/library/functions.html#min), [`max()`](https://python-all.ru/3.16/library/functions.html#max), [`heapq.nlargest()`](https://python-all.ru/3.16/library/heapq.html#heapq.nlargest), [`heapq.nsmallest()`](https://python-all.ru/3.16/library/heapq.html#heapq.nsmallest), [`itertools.groupby()`](https://python-all.ru/3.16/library/itertools.html#itertools.groupby)). Эта функция в первую очередь используется как инструмент перехода для программ, переносимых с Python 2, который поддерживал использование функций сравнения.8485Функция сравнения – это любой вызываемый объект, который принимает два аргумента, сравнивает их и возвращает отрицательное число для «меньше», ноль для «равно» или положительное число для «больше». Функция ключа – это вызываемый объект, который принимает один аргумент и возвращает другое значение, используемое как ключ сортировки.8687Пример:8889```python90sorted(iterable, key=cmp_to_key(locale.strcoll))  # порядок сортировки с учётом локали91```9293Примеры сортировки и краткое руководство по сортировке см. в [Методы сортировки](https://python-all.ru/3.16/howto/sorting.html#sortinghowto).9495Добавлено в версии 3.2.9697#### `@functools.lru_cache(user_function)`9899#### `@functools.lru_cache(maxsize=128, typed=False)`100101Декоратор для обёртывания функции запоминающим вызываемым объектом, который сохраняет до *maxsize* последних вызовов. Это может сэкономить время, когда дорогостоящая или связанная с вводом-выводом функция периодически вызывается с одними и теми же аргументами.102103Кэш потокобезопасен, поэтому обёрнутую функцию можно использовать в нескольких потоках. Это означает, что базовая структура данных будет оставаться согласованной при конкурентных обновлениях.104105Обёрнутая функция может быть вызвана более одного раза, если другой поток делает дополнительный вызов до того, как первый вызов будет завершён и закэширован.106107Поскольку для кэширования результатов используется словарь, позиционные и именованные аргументы функции должны быть [хэшируемыми](https://python-all.ru/3.16/glossary.html#term-hashable).108109Разные шаблоны аргументов могут считаться отдельными вызовами с отдельными записями в кэше. Например, `f(a=1, b=2)` и `f(b=2, a=1)` отличаются порядком именованных аргументов и могут иметь две отдельные записи в кэше.110111Если указан *user\_function*, он должен быть вызываемым. Это позволяет применить декоратор *lru\_cache* непосредственно к пользовательской функции, оставляя *maxsize* со значением по умолчанию 128:112113```python114@lru_cache115def count_vowels(word):116    return sum(word.count(vowel) for vowel in 'AEIOUaeiou')117```118119Если *maxsize* установлен в `None`, механизм LRU отключается и кэш может расти без ограничений.120121Если *typed* установлен в true, аргументы функций разных типов будут кэшироваться отдельно. Если *typed* равен false, реализация обычно считает их эквивалентными вызовами и кэширует только один результат. (Некоторые типы, такие как *str* и *int*, могут кэшироваться отдельно, даже если *typed* равен false.)122123Примечание: учёт типа применяется только к непосредственным аргументам функции, а не к их содержимому. Скалярные аргументы `Decimal(42)` и `Fraction(42)` считаются разными вызовами с разными результатами. Напротив, кортежные аргументы `('answer', Decimal(42))` и `('answer', Fraction(42))` считаются эквивалентными.124125Обёрнутая функция оснащается `cache_parameters()` функцией, которая возвращает новый [`dict`](https://python-all.ru/3.16/library/stdtypes.html#dict), отображающий значения *maxsize* и *typed*. Это только для информационных целей. Изменение значений не влияет на поведение.126127Чтобы оценить эффективность кэша и настроить параметр *maxsize*, обёрнутая функция оснащается `cache_info()` функцией, которая возвращает [именованный кортеж](https://python-all.ru/3.16/glossary.html#term-named-tuple), показывающий *hits*, *misses*, *maxsize* и *currsize*.128129Декоратор также предоставляет функцию `cache_clear()` для очистки или инвалидации кэша.130131Исходная базовая функция доступна через атрибут `__wrapped__`. Это полезно для интроспекции, обхода кэша или повторной обёртки функции другим кэшем.132133Кэш хранит ссылки на аргументы и возвращаемые значения до тех пор, пока они не устареют и не будут вытеснены из кэша, или пока кэш не будет очищен.134135Если кэшируется метод, аргумент экземпляра `self` также попадает в кэш. См. [Как кэшировать вызовы методов?](https://python-all.ru/3.16/faq/programming.html#faq-cache-method-calls)136137[LRU-кэш (вытесняющий давно неиспользовавшиеся элементы)](https://python-all.ru/3.16/library/functools.html) работает лучше всего, когда самые недавние вызовы являются наилучшими предикторами будущих вызовов (например, самые популярные статьи на новостном сервере обычно меняются каждый день). Ограничение размера кэша гарантирует, что кэш не будет расти бесконечно в долго работающих процессах, таких как веб-серверы.138139В целом, LRU-кэш следует использовать только тогда, когда нужно повторно использовать ранее вычисленные значения. Соответственно, не имеет смысла кэшировать функции с побочными эффектами, функции, которые должны создавать различные изменяемые объекты при каждом вызове (например, генераторы и асинхронные функции), или нечистые функции, такие как time() или random().140141Пример LRU-кэша для статического веб-контента:142143```python144@lru_cache(maxsize=32)145def get_pep(num):146    'Retrieve text of a Python Enhancement Proposal'147    resource = f'https://peps.python.org/pep-{num:04d}'148    try:149        with urllib.request.urlopen(resource) as s:150            return s.read()151    except urllib.error.HTTPError:152        return 'Not Found'153154>>> for n in 8, 290, 308, 320, 8, 218, 320, 279, 289, 320, 9991:155...     pep = get_pep(n)156...     print(n, len(pep))157158>>> get_pep.cache_info()159CacheInfo(hits=3, misses=8, maxsize=32, currsize=8)160```161162Пример эффективного вычисления [чисел Фибоначчи](https://python-all.ru/3.16/library/functools.html) с использованием кэша для реализации техники [динамического программирования](https://python-all.ru/3.16/library/functools.html):163164```python165@lru_cache(maxsize=None)166def fib(n):167    if n < 2:168        return n169    return fib(n-1) + fib(n-2)170171>>> [fib(n) for n in range(16)]172[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610]173174>>> fib.cache_info()175CacheInfo(hits=28, misses=16, maxsize=None, currsize=16)176```177178Добавлено в версии 3.2.179180Изменено в версии 3.3: Добавлена опция *typed*.181182Изменено в версии 3.8: Добавлена опция *user\_function*.183184Изменено в версии 3.9: Добавлена функция `cache_parameters()`185186#### `@functools.total_ordering`187188Для класса, определяющего один или несколько методов расширенного сравнения, этот декоратор класса предоставляет остальные. Это упрощает задачу определения всех возможных операций расширенного сравнения:189190Класс должен определять один из [`__lt__()`](https://python-all.ru/3.16/reference/datamodel.html#object.__lt__), [`__le__()`](https://python-all.ru/3.16/reference/datamodel.html#object.__le__), [`__gt__()`](https://python-all.ru/3.16/reference/datamodel.html#object.__gt__) или [`__ge__()`](https://python-all.ru/3.16/reference/datamodel.html#object.__ge__). Кроме того, класс должен предоставлять метод [`__eq__()`](https://python-all.ru/3.16/reference/datamodel.html#object.__eq__).191192Например:193194```python195@total_ordering196class Student:197    def _is_valid_operand(self, other):198        return (hasattr(other, "lastname") and199                hasattr(other, "firstname"))200    def __eq__(self, other):201        if not self._is_valid_operand(other):202            return NotImplemented203        return ((self.lastname.lower(), self.firstname.lower()) ==204                (other.lastname.lower(), other.firstname.lower()))205    def __lt__(self, other):206        if not self._is_valid_operand(other):207            return NotImplemented208        return ((self.lastname.lower(), self.firstname.lower()) <209                (other.lastname.lower(), other.firstname.lower()))210```211212> **Примечание**213>214> Хотя этот декоратор упрощает создание хорошо ведущих себя полностью упорядоченных типов, он *действительно* приводит к более медленному выполнению и более сложным трассировкам стека для производных методов сравнения. Если бенчмаркинг производительности показывает, что это является узким местом для конкретного приложения, реализация всех шести методов расширенного сравнения вместо этого, вероятно, даст лёгкий прирост скорости.215216> **Примечание**217>218> Этот декоратор не пытается переопределить методы, объявленные в классе *или его суперклассах*. Это означает, что если суперкласс определяет оператор сравнения, *total\_ordering* не будет реализовывать его снова, даже если исходный метод является абстрактным.219220Добавлено в версии 3.2.221222Изменено в версии 3.4: Теперь поддерживается возврат `NotImplemented` из базовой функции сравнения для нераспознанных типов.223224#### `functools.Placeholder`225226Объект-одиночка, используемый в качестве стража для резервирования места для позиционных аргументов при вызове [`partial()`](https://python-all.ru/3.16/library/functools.html#functools.partial) и [`partialmethod()`](https://python-all.ru/3.16/library/functools.html#functools.partialmethod).227228Добавлено в версии 3.14.229230#### `functools.partial(func, /, *args, **keywords)`231232Возвращает новый [объект partial](https://python-all.ru/3.16/library/functools.html#partial-objects), который при вызове будет вести себя как *func*, вызванная с позиционными аргументами *args* и именованными аргументами *keywords*. Если при вызове передаётся больше аргументов, они добавляются к *args*. Если передаются дополнительные именованные аргументы, они расширяют и переопределяют *keywords*. Примерно эквивалентно следующему:233234```python235def partial(func, /, *args, **keywords):236    def newfunc(*more_args, **more_keywords):237        return func(*args, *more_args, **(keywords | more_keywords))238    newfunc.func = func239    newfunc.args = args240    newfunc.keywords = keywords241    return newfunc242```243244Функция `partial()` используется для частичного применения функции, которое «замораживает» некоторую часть аргументов и/или именованных аргументов функции, создавая новый объект с упрощённой сигнатурой. Например, `partial()` можно использовать для создания вызываемого объекта, который ведёт себя как функция [`int()`](https://python-all.ru/3.16/library/functions.html#int), у которой аргумент *base* по умолчанию равен `2`:245246```pycon247>>> basetwo = partial(int, base=2)248>>> basetwo.__doc__ = 'Convert base 2 string to an int.'249>>> basetwo('10010')25018251```252253Если в *args* присутствуют сторожевые значения [`Placeholder`](https://python-all.ru/3.16/library/functools.html#functools.Placeholder), они будут заполнены в первую очередь при вызове `partial()`. Это позволяет предварительно заполнить любой позиционный аргумент с помощью вызова `partial()`; без `Placeholder` можно предварительно заполнить только выбранное количество начальных позиционных аргументов.254255Если присутствуют какие-либо сторожевые значения `Placeholder`, все они должны быть заполнены во время вызова:256257```pycon258>>> say_to_world = partial(print, Placeholder, Placeholder, "world!")259>>> say_to_world('Hello', 'dear')260Hello dear world!261```262263Вызов `say_to_world('Hello')` вызывает [`TypeError`](https://python-all.ru/3.16/library/exceptions.html#TypeError), потому что предоставлен только один позиционный аргумент, но есть два заполнителя, которые необходимо заполнить.264265Если `partial()` применяется к существующему объекту `partial()`, сторожевые значения `Placeholder` входного объекта заполняются новыми позиционными аргументами. Заполнитель можно сохранить, вставив новый сторож `Placeholder` на место, занимаемое предыдущим `Placeholder`:266267```pycon268>>> from functools import partial, Placeholder as _269>>> remove = partial(str.replace, _, _, '')270>>> message = 'Hello, dear dear world!'271>>> remove(message, ' dear')272'Hello, world!'273>>> remove_dear = partial(remove, _, ' dear')274>>> remove_dear(message)275'Hello, world!'276>>> remove_first_dear = partial(remove_dear, _, 1)277>>> remove_first_dear(message)278'Hello, dear world!'279```280281`Placeholder` нельзя передать в `partial()` как именованный аргумент.282283Изменено в версии 3.14: Добавлена поддержка [`Placeholder`](https://python-all.ru/3.16/library/functools.html#functools.Placeholder) в позиционных аргументах.284285#### `class functools.partialmethod(func, /, *args, **keywords)`286287Возвращает новый дескриптор `partialmethod`, который ведёт себя как [`partial`](https://python-all.ru/3.16/library/functools.html#functools.partial), за исключением того, что он предназначен для использования в качестве определения метода, а не для прямого вызова.288289*func* должен быть [дескриптором](https://python-all.ru/3.16/glossary.html#term-descriptor) или вызываемым объектом (объекты, являющиеся и тем и другим, как обычные функции, обрабатываются как дескрипторы).290291Когда *func* является дескриптором (например, обычная функция Python, [`classmethod()`](https://python-all.ru/3.16/library/functions.html#classmethod), [`staticmethod()`](https://python-all.ru/3.16/library/functions.html#staticmethod), [`abstractmethod()`](https://python-all.ru/3.16/library/abc.html#abc.abstractmethod) или другой экземпляр `partialmethod`), вызовы `__get__` делегируются базовому дескриптору, а в качестве результата возвращается подходящий [частичный объект](https://python-all.ru/3.16/library/functools.html#partial-objects).292293Когда *func* – это вызываемый объект, не являющийся дескриптором, динамически создаётся соответствующий связанный метод. Он ведёт себя как обычная функция Python при использовании в качестве метода: аргумент *self* будет вставлен как первый позиционный аргумент, даже до *args* и *keywords*, переданных конструктору `partialmethod`.294295Пример:296297```python298>>> class Cell:299...     def __init__(self):300...         self._alive = False301...     @property302...     def alive(self):303...         return self._alive304...     def set_state(self, state):305...         self._alive = bool(state)306...     set_alive = partialmethod(set_state, True)307...     set_dead = partialmethod(set_state, False)308...309>>> c = Cell()310>>> c.alive311False312>>> c.set_alive()313>>> c.alive314True315```316317Добавлено в версии 3.4.318319#### `functools.reduce(function, iterable, /[, initial])`320321Применяет *function* от двух аргументов кумулятивно к элементам *iterable* слева направо, чтобы свести итерабельный объект к одному значению. Например, `reduce(lambda x, y: x+y, [1, 2, 3, 4, 5])` вычисляет `((((1+2)+3)+4)+5)`. Левый аргумент *x* – это накопленное значение, а правый аргумент *y* – это обновляемое значение из *iterable*. Если указан необязательный *initial*, он помещается перед элементами итерабельного объекта в вычислениях и служит значением по умолчанию, когда итерабельный объект пуст. Если *initial* не задан, а *iterable* содержит только один элемент, возвращается первый элемент.322323Примерно эквивалентно:324325```python326initial_missing = sentinel('initial_missing')327328def reduce(function, iterable, /, initial=initial_missing):329    it = iter(iterable)330    if initial is initial_missing:331        value = next(it)332    else:333        value = initial334    for element in it:335        value = function(value, element)336    return value337```338339См. [`itertools.accumulate()`](https://python-all.ru/3.16/library/itertools.html#itertools.accumulate) для итератора, который возвращает все промежуточные значения.340341Изменено в версии 3.14: *initial* теперь поддерживается как именованный аргумент.342343#### `@functools.singledispatch`344345Преобразует функцию в [однодиспетчерскую](https://python-all.ru/3.16/glossary.html#term-single-dispatch) [обобщённую функцию](https://python-all.ru/3.16/glossary.html#term-generic-function).346347Чтобы определить обобщённую функцию, декорируйте её с помощью декоратора `@singledispatch`. При определении функции с помощью `@singledispatch` учтите, что диспетчеризация происходит по типу первого аргумента:348349```python350>>> from functools import singledispatch351>>> @singledispatch352... def fun(arg, verbose=False):353...     if verbose:354...         print("Let me just say,", end=" ")355...     print(arg)356```357358Чтобы добавить перегруженные реализации функции, используйте атрибут `register()` обобщённой функции, который можно использовать как декоратор. Для функций с аннотациями типов декоратор автоматически определяет тип первого аргумента:359360```python361>>> @fun.register362... def _(arg: int, verbose=False):363...     if verbose:364...         print("Strength in numbers, eh?", end=" ")365...     print(arg)366...367>>> @fun.register368... def _(arg: list, verbose=False):369...     if verbose:370...         print("Enumerate this:")371...     for i, elem in enumerate(arg):372...         print(i, elem)373```374375[`typing.Union`](https://python-all.ru/3.16/library/typing.html#typing.Union) также можно использовать:376377```python378>>> @fun.register379... def _(arg: int | float, verbose=False):380...     if verbose:381...         print("Strength in numbers, eh?", end=" ")382...     print(arg)383...384>>> from typing import Union385>>> @fun.register386... def _(arg: Union[list, set], verbose=False):387...     if verbose:388...         print("Enumerate this:")389...     for i, elem in enumerate(arg):390...         print(i, elem)391...392```393394Для кода, не использующего аннотации типов, подходящий тип можно передать явно самому декоратору:395396```python397>>> @fun.register(complex)398... def _(arg, verbose=False):399...     if verbose:400...         print("Better than complicated.", end=" ")401...     print(arg.real, arg.imag)402...403```404405Для кода, который выполняет диспетчеризацию по типу коллекции (например, `list`), но хочет аннотировать типы элементов коллекции (например, `list[int]`), тип для диспетчеризации следует явно передать самому декоратору, а аннотация типа указывается в определении функции:406407```python408>>> @fun.register(list)409... def _(arg: list[int], verbose=False):410...     if verbose:411...         print("Enumerate this:")412...     for i, elem in enumerate(arg):413...         print(i, elem)414```415416> **Примечание**417>418> Во время выполнения функция будет выполнять диспетчеризацию по экземпляру списка независимо от типа элементов внутри списка, то есть `[1,2,3]` будет диспетчеризоваться так же, как `["foo", "bar", "baz"]`. Аннотация, приведённая в этом примере, предназначена только для статических проверок типов и не влияет на выполнение.419420Чтобы можно было регистрировать [лямбды](https://python-all.ru/3.16/glossary.html#term-lambda) и уже существующие функции, атрибут [`register()`](https://python-all.ru/3.16/library/functools.html#functools.singledispatch.register) также можно использовать в функциональной форме:421422```python423>>> def nothing(arg, verbose=False):424...     print("Nothing.")425...426>>> fun.register(type(None), nothing)427```428429Атрибут [`register()`](https://python-all.ru/3.16/library/functools.html#functools.singledispatch.register) возвращает недекорированную функцию. Это позволяет накладывать декораторы, [`pickling`](https://python-all.ru/3.16/library/pickle.html#module-pickle) и создавать модульные тесты для каждого варианта независимо:430431```python432>>> @fun.register(float)433... @fun.register(Decimal)434... def fun_num(arg, verbose=False):435...     if verbose:436...         print("Half of your number:", end=" ")437...     print(arg / 2)438...439>>> fun_num is fun440False441```442443При вызове обобщённая функция выполняет диспетчеризацию по типу первого аргумента:444445```python446>>> fun("Hello, world.")447Hello, world.448>>> fun("test.", verbose=True)449Let me just say, test.450>>> fun(42, verbose=True)451Strength in numbers, eh? 42452>>> fun(['spam', 'spam', 'eggs', 'spam'], verbose=True)453Enumerate this:4540 spam4551 spam4562 eggs4573 spam458>>> fun(None)459Nothing.460>>> fun(1.23)4610.615462```463464Если для конкретного типа нет зарегистрированной реализации, используется порядок разрешения методов (MRO) этого типа для поиска более общей реализации. Исходная функция, декорированная `@singledispatch`, зарегистрирована для базового типа [`object`](https://python-all.ru/3.16/library/functions.html#object), то есть она используется, если не найдено более подходящей реализации.465466Если реализация зарегистрирована для [абстрактного базового класса](https://python-all.ru/3.16/glossary.html#term-abstract-base-class), виртуальные подклассы этого базового класса будут диспетчеризоваться к этой реализации:467468```python469>>> from collections.abc import Mapping470>>> @fun.register471... def _(arg: Mapping, verbose=False):472...     if verbose:473...         print("Keys & Values")474...     for key, value in arg.items():475...         print(key, "=>", value)476...477>>> fun({"a": "b"})478a => b479```480481Чтобы проверить, какую реализацию выберет обобщённая функция для заданного типа, используйте атрибут `dispatch()`:482483```python484>>> fun.dispatch(float)485<function fun_num at 0x1035a2840>486>>> fun.dispatch(dict)    # примечание: реализация по умолчанию487<function fun at 0x103fe0000>488```489490Чтобы получить доступ ко всем зарегистрированным реализациям, используйте атрибут `registry` только для чтения:491492```python493>>> fun.registry.keys()494dict_keys([<class 'NoneType'>, <class 'int'>, <class 'object'>,495          <class 'decimal.Decimal'>, <class 'list'>,496          <class 'float'>])497>>> fun.registry[float]498<function fun_num at 0x1035a2840>499>>> fun.registry[object]500<function fun at 0x103fe0000>501```502503Добавлено в версии 3.4.504505Изменено в версии 3.7: Атрибут [`register()`](https://python-all.ru/3.16/library/functools.html#functools.singledispatch.register) теперь поддерживает использование аннотаций типов.506507Изменено в версии 3.11: Атрибут [`register()`](https://python-all.ru/3.16/library/functools.html#functools.singledispatch.register) теперь поддерживает [`typing.Union`](https://python-all.ru/3.16/library/typing.html#typing.Union) в качестве аннотации типа.508509#### `class functools.singledispatchmethod(func)`510511Преобразует метод в [однодиспетчерскую](https://python-all.ru/3.16/glossary.html#term-single-dispatch) [обобщённую функцию](https://python-all.ru/3.16/glossary.html#term-generic-function).512513Чтобы определить обобщённый метод, декорируйте его с помощью декоратора `@singledispatchmethod`. При определении метода с помощью `@singledispatchmethod` учтите, что диспетчеризация происходит по типу первого аргумента, не являющегося *self* или *cls*:514515```python516class Negator:517    @singledispatchmethod518    def neg(self, arg):519        raise NotImplementedError("Cannot negate a")520521    @neg.register522    def _(self, arg: int):523        return -arg524525    @neg.register526    def _(self, arg: bool):527        return not arg528```529530`@singledispatchmethod` поддерживает вложение с другими декораторами, такими как [`@classmethod`](https://python-all.ru/3.16/library/functions.html#classmethod). Обратите внимание, что для поддержки `dispatcher.register` `singledispatchmethod` должен быть *самым внешним* декоратором. Вот класс `Negator` с методами `neg`, привязанными к классу, а не к экземпляру класса:531532```python533class Negator:534    @singledispatchmethod535    @classmethod536    def neg(cls, arg):537        raise NotImplementedError("Cannot negate a")538539    @neg.register540    @classmethod541    def _(cls, arg: int):542        return -arg543544    @neg.register545    @classmethod546    def _(cls, arg: bool):547        return not arg548```549550Тот же шаблон можно использовать для других подобных декораторов: [`@staticmethod`](https://python-all.ru/3.16/library/functions.html#staticmethod), [`@~abc.abstractmethod`](https://python-all.ru/3.16/library/abc.html#abc.abstractmethod) и других.551552Добавлено в версии 3.8.553554Изменено в версии 3.15: Добавлена поддержка вызываемых объектов, не являющихся [дескрипторами](https://python-all.ru/3.16/glossary.html#term-descriptor).555556#### `functools.update_wrapper(wrapper, wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)`557558Обновляет функцию-*обёртку* так, чтобы она выглядела как *обёрнутую* функцию. Необязательные аргументы – кортежи, определяющие, какие атрибуты исходной функции напрямую присваиваются соответствующим атрибутам функции-обёртки, а какие атрибуты функции-обёртки обновляются соответствующими атрибутами исходной функции. Значения по умолчанию для этих аргументов – константы уровня модуля `WRAPPER_ASSIGNMENTS` (которая присваивает функции-обёртке атрибуты [`__module__`](https://python-all.ru/3.16/reference/datamodel.html#function.__module__), [`__name__`](https://python-all.ru/3.16/reference/datamodel.html#function.__name__), [`__qualname__`](https://python-all.ru/3.16/reference/datamodel.html#function.__qualname__), [`__annotations__`](https://python-all.ru/3.16/reference/datamodel.html#function.__annotations__), [`__type_params__`](https://python-all.ru/3.16/reference/datamodel.html#function.__type_params__) и [`__doc__`](https://python-all.ru/3.16/reference/datamodel.html#function.__doc__) – строку документации) и `WRAPPER_UPDATES` (которая обновляет [`__dict__`](https://python-all.ru/3.16/reference/datamodel.html#function.__dict__) функции-обёртки, т.е. словарь экземпляра).559560Чтобы обеспечить доступ к исходной функции для интроспекции и других целей (например, обхода кэширующего декоратора такого как [`@lru_cache`](https://python-all.ru/3.16/library/functools.html#functools.lru_cache)), эта функция автоматически добавляет атрибут `__wrapped__` к обёртке, который ссылается на обёрнутую функцию.561562Основное назначение этой функции – использование в [decorator](https://python-all.ru/3.16/glossary.html#term-decorator)-функциях, которые оборачивают декорированную функцию и возвращают обёртку. Если функция-обёртка не обновляется, метаданные возвращаемой функции будут отражать определение обёртки, а не исходной функции, что обычно не очень полезно.563564`update_wrapper()` можно использовать с вызываемыми объектами, отличными от функций. Любые атрибуты, указанные в *assigned* или *updated*, отсутствующие в оборачиваемом объекте, игнорируются (т.е. эта функция не будет пытаться установить их в функции-обёртке). [`AttributeError`](https://python-all.ru/3.16/library/exceptions.html#AttributeError) по-прежнему возбуждается, если сама функция-обёртка не имеет каких-либо атрибутов, перечисленных в *updated*.565566Изменено в версии 3.2: Атрибут `__wrapped__` теперь добавляется автоматически. Атрибут [`__annotations__`](https://python-all.ru/3.16/reference/datamodel.html#function.__annotations__) теперь копируется по умолчанию. Отсутствующие атрибуты больше не вызывают [`AttributeError`](https://python-all.ru/3.16/library/exceptions.html#AttributeError).567568Изменено в версии 3.4: Атрибут `__wrapped__` теперь всегда ссылается на обёрнутую функцию, даже если эта функция определяла атрибут `__wrapped__`. (см. [bpo-17482](https://python-all.ru/3.16/library/functools.html))569570Изменено в версии 3.12: Атрибут [`__type_params__`](https://python-all.ru/3.16/reference/datamodel.html#function.__type_params__) теперь копируется по умолчанию.571572#### `@functools.wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)`573574Это удобная функция для вызова [`update_wrapper()`](https://python-all.ru/3.16/library/functools.html#functools.update_wrapper) в качестве декоратора функции при определении функции-обёртки. Она эквивалентна `partial(update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated)`. Например:575576```python577>>> from functools import wraps578>>> def my_decorator(f):579...     @wraps(f)580...     def wrapper(*args, **kwds):581...         print('Calling decorated function')582...         return f(*args, **kwds)583...     return wrapper584...585>>> @my_decorator586... def example():587...     """докстринг"""588...     print('Called example function')589...590>>> example()591Calling decorated function592Called example function593>>> example.__name__594'example'595>>> example.__doc__596'Docstring'597```598599Без использования этой фабрики декораторов имя примера функции было бы `'wrapper'`, а строка документации исходной `example()` была бы потеряна.600601## [`partial`](https://python-all.ru/3.16/library/functools.html#functools.partial) Объекты602603Объекты [`partial`](https://python-all.ru/3.16/library/functools.html#functools.partial) – это вызываемые объекты, создаваемые [`partial()`](https://python-all.ru/3.16/library/functools.html#functools.partial). Они имеют три атрибута только для чтения:604605#### `partial.func`606607Вызываемый объект или функция. Вызовы объекта [`partial`](https://python-all.ru/3.16/library/functools.html#functools.partial) будут перенаправлены в [`func`](https://python-all.ru/3.16/library/functools.html#functools.partial.func) с новыми аргументами и ключевыми словами.608609#### `partial.args`610611Крайние левые позиционные аргументы, которые будут добавлены перед позиционными аргументами, переданными при вызове объекта [`partial`](https://python-all.ru/3.16/library/functools.html#functools.partial).612613#### `partial.keywords`614615Ключевые аргументы, которые будут предоставлены при вызове объекта [`partial`](https://python-all.ru/3.16/library/functools.html#functools.partial).616617Объекты [`partial`](https://python-all.ru/3.16/library/functools.html#functools.partial) похожи на [объекты функций](https://python-all.ru/3.16/reference/datamodel.html#user-defined-funcs) тем, что они вызываемы, поддерживают слабые ссылки и могут иметь атрибуты. Есть несколько важных отличий. Например, атрибуты [`__name__`](https://python-all.ru/3.16/library/stdtypes.html#definition.__name__) и [`__doc__`](https://python-all.ru/3.16/library/stdtypes.html#definition.__doc__) не создаются автоматически.618