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

functools.md

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

1> **Источник:** https://python-all.ru/3.12/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.12/Lib/functools.py)1011---1213Модуль [`functools`](https://python-all.ru/3.12/library/functools.html#module-functools) предназначен для функций высшего порядка: функций, которые действуют на или возвращают другие функции. В общем случае любой вызываемый объект может рассматриваться как функция для целей этого модуля.1415Модуль [`functools`](https://python-all.ru/3.12/library/functools.html#module-functools) определяет следующие функции:1617#### `@functools.cache(user_function)`1819Простой легковесный кэш функций без ограничения размера. Иногда называется [«мемоизация»](https://python-all.ru/3.12/library/functools.html).2021Возвращает то же, что и `lru_cache(maxsize=None)`, создавая тонкую обёртку вокруг поиска по словарю для аргументов функции. Поскольку ему никогда не нужно вытеснять старые значения, он меньше и быстрее, чем [`lru_cache()`](https://python-all.ru/3.12/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)      # делает два новых рекурсивных вызова, остальные 10 закешированы3547900160036```3738Кэш потокобезопасен, поэтому обёрнутую функцию можно использовать в нескольких потоках. Это означает, что базовая структура данных будет оставаться согласованной при конкурентных обновлениях.3940Обёрнутая функция может быть вызвана более одного раза, если другой поток делает дополнительный вызов до того, как первый вызов будет завершён и закэширован.4142Добавлено в версии 3.9.4344#### `@functools.cached_property(func)`4546Преобразует метод класса в свойство, значение которого вычисляется один раз и затем кэшируется как обычный атрибут на всё время существования экземпляра. Аналогично [`property()`](https://python-all.ru/3.12/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()`](https://python-all.ru/3.12/library/functools.html#functools.cached_property) несколько отличается от [`property()`](https://python-all.ru/3.12/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.12/library/functools.html) словарей с разделением ключей. Это означает, что словари экземпляров могут занимать больше места, чем обычно.7071Кроме того, этот декоратор требует, чтобы атрибут `__dict__` на каждом экземпляре был изменяемым отображением. Это означает, что он не будет работать с некоторыми типами, такими как метаклассы (поскольку атрибуты `__dict__` на экземплярах типа являются прокси только для чтения пространства имён класса), и теми, которые указывают `__slots__` без включения `__dict__` в качестве одного из определённых слотов (так как такие классы вообще не предоставляют атрибут `__dict__`).7273Если изменяемое отображение недоступно или требуется эффективное по памяти разделение ключей, эффекта, аналогичного [`cached_property()`](https://python-all.ru/3.12/library/functools.html#functools.cached_property), можно также достичь, разместив [`property()`](https://python-all.ru/3.12/library/functions.html#property) поверх [`lru_cache()`](https://python-all.ru/3.12/library/functools.html#functools.lru_cache). Смотрите [Как кэшировать вызовы методов?](https://python-all.ru/3.12/faq/programming.html#faq-cache-method-calls) для получения дополнительных сведений о том, чем это отличается от [`cached_property()`](https://python-all.ru/3.12/library/functools.html#functools.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.12/glossary.html#term-key-function). Используется с инструментами, которые принимают функции ключа (такими как [`sorted()`](https://python-all.ru/3.12/library/functions.html#sorted), [`min()`](https://python-all.ru/3.12/library/functions.html#min), [`max()`](https://python-all.ru/3.12/library/functions.html#max), [`heapq.nlargest()`](https://python-all.ru/3.12/library/heapq.html#heapq.nlargest), [`heapq.nsmallest()`](https://python-all.ru/3.12/library/heapq.html#heapq.nsmallest), [`itertools.groupby()`](https://python-all.ru/3.12/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.12/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.12/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.12/library/stdtypes.html#dict), отображающий значения *maxsize* и *typed*. Это только для информационных целей. Изменение значений не влияет на поведение.124125Чтобы оценить эффективность кэша и настроить параметр *maxsize*, обёрнутая функция оснащается `cache_info()` функцией, которая возвращает [именованный кортеж](https://python-all.ru/3.12/glossary.html#term-named-tuple), показывающий *hits*, *misses*, *maxsize* и *currsize*.126127Декоратор также предоставляет функцию `cache_clear()` для очистки или инвалидации кэша.128129Исходная базовая функция доступна через атрибут `__wrapped__`. Это полезно для интроспекции, обхода кэша или повторной обёртки функции другим кэшем.130131Кэш хранит ссылки на аргументы и возвращаемые значения до тех пор, пока они не устареют и не будут вытеснены из кэша, или пока кэш не будет очищен.132133Если кэшируется метод, аргумент экземпляра `self` также попадает в кэш. См. [Как кэшировать вызовы методов?](https://python-all.ru/3.12/faq/programming.html#faq-cache-method-calls)134135[LRU-кэш (вытесняющий давно неиспользовавшиеся элементы)](https://python-all.ru/3.12/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.12/library/functools.html) с использованием кэша для реализации техники [динамического программирования](https://python-all.ru/3.12/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__()`, `__le__()`, `__gt__()` или `__ge__()`. Кроме того, класс должен предоставлять метод `__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.partial(func, /, *args, **keywords)`223224Возвращает новый [объект partial](https://python-all.ru/3.12/library/functools.html#partial-objects), который при вызове будет вести себя как *func*, вызванная с позиционными аргументами *args* и именованными аргументами *keywords*. Если при вызове передаётся больше аргументов, они добавляются к *args*. Если передаются дополнительные именованные аргументы, они расширяют и переопределяют *keywords*. Примерно эквивалентно следующему:225226```python227def partial(func, /, *args, **keywords):228    def newfunc(*fargs, **fkeywords):229        newkeywords = {**keywords, **fkeywords}230        return func(*args, *fargs, **newkeywords)231    newfunc.func = func232    newfunc.args = args233    newfunc.keywords = keywords234    return newfunc235```236237[`partial()`](https://python-all.ru/3.12/library/functools.html#functools.partial) используется для частичного применения функции, которое «замораживает» некоторую часть аргументов и/или ключевых слов функции, создавая новый объект с упрощённой сигнатурой. Например, [`partial()`](https://python-all.ru/3.12/library/functools.html#functools.partial) можно использовать для создания вызываемого объекта, который ведёт себя как функция [`int()`](https://python-all.ru/3.12/library/functions.html#int), где аргумент *base* по умолчанию равен двум:238239```python240>>> from functools import partial241>>> basetwo = partial(int, base=2)242>>> basetwo.__doc__ = 'Convert base 2 string to an int.'243>>> basetwo('10010')24418245```246247#### `class functools.partialmethod(func, /, *args, **keywords)`248249Возвращает новый дескриптор [`partialmethod`](https://python-all.ru/3.12/library/functools.html#functools.partialmethod), который ведёт себя как [`partial`](https://python-all.ru/3.12/library/functools.html#functools.partial), за исключением того, что он предназначен для использования в качестве определения метода, а не для прямого вызова.250251*func* должен быть [дескриптором](https://python-all.ru/3.12/glossary.html#term-descriptor) или вызываемым объектом (объекты, являющиеся и тем и другим, как обычные функции, обрабатываются как дескрипторы).252253Когда *func* является дескриптором (например, обычная функция Python, [`classmethod()`](https://python-all.ru/3.12/library/functions.html#classmethod), [`staticmethod()`](https://python-all.ru/3.12/library/functions.html#staticmethod), `abstractmethod()` или другой экземпляр [`partialmethod`](https://python-all.ru/3.12/library/functools.html#functools.partialmethod)), вызовы `__get__` делегируются базовому дескриптору, а в качестве результата возвращается подходящий [частичный объект](https://python-all.ru/3.12/library/functools.html#partial-objects).254255Когда *func* – это вызываемый объект, не являющийся дескриптором, динамически создаётся соответствующий связанный метод. Он ведёт себя как обычная функция Python при использовании в качестве метода: аргумент *self* будет вставлен как первый позиционный аргумент, даже до *args* и *keywords*, переданных конструктору [`partialmethod`](https://python-all.ru/3.12/library/functools.html#functools.partialmethod).256257Пример:258259```python260>>> class Cell:261...     def __init__(self):262...         self._alive = False263...     @property264...     def alive(self):265...         return self._alive266...     def set_state(self, state):267...         self._alive = bool(state)268...     set_alive = partialmethod(set_state, True)269...     set_dead = partialmethod(set_state, False)270...271>>> c = Cell()272>>> c.alive273False274>>> c.set_alive()275>>> c.alive276True277```278279Добавлено в версии 3.4.280281#### `functools.reduce(function, iterable[, initializer])`282283Применяет *функцию* с двумя аргументами последовательно к элементам *итерируемого объекта* слева направо, чтобы свести итерируемый объект к одному значению. Например, `reduce(lambda x, y: x+y, [1, 2, 3, 4, 5])` вычисляет `((((1+2)+3)+4)+5)`. Левый аргумент *x* – накопленное значение, а правый аргумент *y* – очередное значение из *итерируемого объекта*. Если указан необязательный *инициализатор*, он помещается перед элементами итерируемого объекта в вычислениях и служит значением по умолчанию, когда итерируемый объект пуст. Если *инициализатор* не указан, а *итерируемый объект* содержит только один элемент, возвращается первый элемент.284285Примерно эквивалентно:286287```python288def reduce(function, iterable, initializer=None):289    it = iter(iterable)290    if initializer is None:291        value = next(it)292    else:293        value = initializer294    for element in it:295        value = function(value, element)296    return value297```298299См. [`itertools.accumulate()`](https://python-all.ru/3.12/library/itertools.html#itertools.accumulate) для итератора, который возвращает все промежуточные значения.300301#### `@functools.singledispatch`302303Преобразует функцию в [однодиспетчерскую](https://python-all.ru/3.12/glossary.html#term-single-dispatch) [обобщённую функцию](https://python-all.ru/3.12/glossary.html#term-generic-function).304305Чтобы определить обобщённую функцию, декорируйте её с помощью декоратора `@singledispatch`. При определении функции с помощью `@singledispatch` учтите, что диспетчеризация происходит по типу первого аргумента:306307```python308>>> from functools import singledispatch309>>> @singledispatch310... def fun(arg, verbose=False):311...     if verbose:312...         print("Let me just say,", end=" ")313...     print(arg)314```315316Чтобы добавить перегруженные реализации функции, используйте атрибут `register()` обобщённой функции, который можно использовать как декоратор. Для функций с аннотациями типов декоратор автоматически определяет тип первого аргумента:317318```python319>>> @fun.register320... def _(arg: int, verbose=False):321...     if verbose:322...         print("Strength in numbers, eh?", end=" ")323...     print(arg)324...325>>> @fun.register326... def _(arg: list, verbose=False):327...     if verbose:328...         print("Enumerate this:")329...     for i, elem in enumerate(arg):330...         print(i, elem)331```332333[`types.UnionType`](https://python-all.ru/3.12/library/types.html#types.UnionType) и [`typing.Union`](https://python-all.ru/3.12/library/typing.html#typing.Union) также могут использоваться:334335```python336>>> @fun.register337... def _(arg: int | float, verbose=False):338...     if verbose:339...         print("Strength in numbers, eh?", end=" ")340...     print(arg)341...342>>> from typing import Union343>>> @fun.register344... def _(arg: Union[list, set], verbose=False):345...     if verbose:346...         print("Enumerate this:")347...     for i, elem in enumerate(arg):348...         print(i, elem)349...350```351352Для кода, не использующего аннотации типов, подходящий тип можно передать явно самому декоратору:353354```python355>>> @fun.register(complex)356... def _(arg, verbose=False):357...     if verbose:358...         print("Better than complicated.", end=" ")359...     print(arg.real, arg.imag)360...361```362363Для кода, который выполняет диспетчеризацию по типу коллекции (например, `list`), но хочет аннотировать типы элементов коллекции (например, `list[int]`), тип для диспетчеризации следует явно передать самому декоратору, а аннотация типа указывается в определении функции:364365```python366>>> @fun.register(list)367... def _(arg: list[int], verbose=False):368...     if verbose:369...         print("Enumerate this:")370...     for i, elem in enumerate(arg):371...         print(i, elem)372```373374> **Примечание**375>376> Во время выполнения функция будет выполнять диспетчеризацию по экземпляру списка независимо от типа элементов внутри списка, то есть `[1,2,3]` будет диспетчеризоваться так же, как `["foo", "bar", "baz"]`. Аннотация, приведённая в этом примере, предназначена только для статических проверок типов и не влияет на выполнение.377378Чтобы можно было регистрировать [лямбды](https://python-all.ru/3.12/glossary.html#term-lambda) и уже существующие функции, атрибут `register()` также можно использовать в функциональной форме:379380```python381>>> def nothing(arg, verbose=False):382...     print("Nothing.")383...384>>> fun.register(type(None), nothing)385```386387Атрибут `register()` возвращает недекорированную функцию. Это позволяет накладывать декораторы, [`pickling`](https://python-all.ru/3.12/library/pickle.html#module-pickle) и создавать модульные тесты для каждого варианта независимо:388389```python390>>> @fun.register(float)391... @fun.register(Decimal)392... def fun_num(arg, verbose=False):393...     if verbose:394...         print("Half of your number:", end=" ")395...     print(arg / 2)396...397>>> fun_num is fun398False399```400401При вызове обобщённая функция выполняет диспетчеризацию по типу первого аргумента:402403```python404>>> fun("Hello, world.")405Hello, world.406>>> fun("test.", verbose=True)407Let me just say, test.408>>> fun(42, verbose=True)409Strength in numbers, eh? 42410>>> fun(['spam', 'spam', 'eggs', 'spam'], verbose=True)411Enumerate this:4120 spam4131 spam4142 eggs4153 spam416>>> fun(None)417Nothing.418>>> fun(1.23)4190.615420```421422Если для конкретного типа нет зарегистрированной реализации, используется порядок разрешения методов (MRO) этого типа для поиска более общей реализации. Исходная функция, декорированная `@singledispatch`, зарегистрирована для базового типа [`object`](https://python-all.ru/3.12/library/functions.html#object), то есть она используется, если не найдено более подходящей реализации.423424Если реализация зарегистрирована для [абстрактного базового класса](https://python-all.ru/3.12/glossary.html#term-abstract-base-class), виртуальные подклассы этого базового класса будут диспетчеризоваться к этой реализации:425426```python427>>> from collections.abc import Mapping428>>> @fun.register429... def _(arg: Mapping, verbose=False):430...     if verbose:431...         print("Keys & Values")432...     for key, value in arg.items():433...         print(key, "=>", value)434...435>>> fun({"a": "b"})436a => b437```438439Чтобы проверить, какую реализацию выберет обобщённая функция для заданного типа, используйте атрибут `dispatch()`:440441```python442>>> fun.dispatch(float)443<function fun_num at 0x1035a2840>444>>> fun.dispatch(dict)    # примечание: реализация по умолчанию445<function fun at 0x103fe0000>446```447448Чтобы получить доступ ко всем зарегистрированным реализациям, используйте атрибут `registry` только для чтения:449450```python451>>> fun.registry.keys()452dict_keys([<class 'NoneType'>, <class 'int'>, <class 'object'>,453          <class 'decimal.Decimal'>, <class 'list'>,454          <class 'float'>])455>>> fun.registry[float]456<function fun_num at 0x1035a2840>457>>> fun.registry[object]458<function fun at 0x103fe0000>459```460461Добавлено в версии 3.4.462463Изменено в версии 3.7: Атрибут `register()` теперь поддерживает использование аннотаций типов.464465Изменено в версии 3.11: Атрибут `register()` теперь поддерживает [`types.UnionType`](https://python-all.ru/3.12/library/types.html#types.UnionType) и [`typing.Union`](https://python-all.ru/3.12/library/typing.html#typing.Union) в качестве аннотаций типов.466467#### `class functools.singledispatchmethod(func)`468469Преобразует метод в [однодиспетчерскую](https://python-all.ru/3.12/glossary.html#term-single-dispatch) [обобщённую функцию](https://python-all.ru/3.12/glossary.html#term-generic-function).470471Чтобы определить обобщённый метод, декорируйте его декоратором `@singledispatchmethod`. При определении функции с помощью `@singledispatchmethod` учтите, что диспетчеризация выполняется по типу первого аргумента, отличного от *self* или *cls*:472473```python474class Negator:475    @singledispatchmethod476    def neg(self, arg):477        raise NotImplementedError("Cannot negate a")478479    @neg.register480    def _(self, arg: int):481        return -arg482483    @neg.register484    def _(self, arg: bool):485        return not arg486```487488`@singledispatchmethod` поддерживает вложение с другими декораторами, такими как [`@classmethod`](https://python-all.ru/3.12/library/functions.html#classmethod). Обратите внимание, что для поддержки `dispatcher.register` `singledispatchmethod` должен быть *самым внешним* декоратором. Вот класс `Negator` с методами `neg`, привязанными к классу, а не к экземпляру класса:489490```python491class Negator:492    @singledispatchmethod493    @classmethod494    def neg(cls, arg):495        raise NotImplementedError("Cannot negate a")496497    @neg.register498    @classmethod499    def _(cls, arg: int):500        return -arg501502    @neg.register503    @classmethod504    def _(cls, arg: bool):505        return not arg506```507508Этот же подход можно использовать для других подобных декораторов: [`@staticmethod`](https://python-all.ru/3.12/library/functions.html#staticmethod), [`@abstractmethod`](https://python-all.ru/3.12/library/abc.html#abc.abstractmethod) и других.509510Добавлено в версии 3.8.511512#### `functools.update_wrapper(wrapper, wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)`513514Обновляет функцию-*обёртку* так, чтобы она выглядела как *обёрнутую* функцию. Необязательные аргументы – кортежи, определяющие, какие атрибуты исходной функции напрямую присваиваются соответствующим атрибутам функции-обёртки, а какие атрибуты функции-обёртки обновляются соответствующими атрибутами исходной функции. Значения по умолчанию для этих аргументов – константы уровня модуля `WRAPPER_ASSIGNMENTS` (которая присваивает функции-обёртке атрибуты [`__module__`](https://python-all.ru/3.12/reference/datamodel.html#function.__module__), [`__name__`](https://python-all.ru/3.12/reference/datamodel.html#function.__name__), [`__qualname__`](https://python-all.ru/3.12/reference/datamodel.html#function.__qualname__), [`__annotations__`](https://python-all.ru/3.12/reference/datamodel.html#function.__annotations__), [`__type_params__`](https://python-all.ru/3.12/reference/datamodel.html#function.__type_params__) и [`__doc__`](https://python-all.ru/3.12/reference/datamodel.html#function.__doc__) – строку документации) и `WRAPPER_UPDATES` (которая обновляет [`__dict__`](https://python-all.ru/3.12/reference/datamodel.html#function.__dict__) функции-обёртки, т.е. словарь экземпляра).515516Чтобы обеспечить доступ к исходной функции для интроспекции и других целей (например, обхода кэширующего декоратора такого как [`lru_cache()`](https://python-all.ru/3.12/library/functools.html#functools.lru_cache)), эта функция автоматически добавляет атрибут `__wrapped__` к обёртке, который ссылается на обёрнутую функцию.517518Основное назначение этой функции – использование в [decorator](https://python-all.ru/3.12/glossary.html#term-decorator)-функциях, которые оборачивают декорированную функцию и возвращают обёртку. Если функция-обёртка не обновляется, метаданные возвращаемой функции будут отражать определение обёртки, а не исходной функции, что обычно не очень полезно.519520[`update_wrapper()`](https://python-all.ru/3.12/library/functools.html#functools.update_wrapper) можно использовать с вызываемыми объектами, отличными от функций. Любые атрибуты, указанные в *assigned* или *updated*, отсутствующие в оборачиваемом объекте, игнорируются (т.е. эта функция не будет пытаться установить их в функции-обёртке). [`AttributeError`](https://python-all.ru/3.12/library/exceptions.html#AttributeError) по-прежнему возбуждается, если сама функция-обёртка не имеет каких-либо атрибутов, перечисленных в *updated*.521522Изменено в версии 3.2: Атрибут `__wrapped__` теперь добавляется автоматически. Атрибут [`__annotations__`](https://python-all.ru/3.12/reference/datamodel.html#function.__annotations__) теперь копируется по умолчанию. Отсутствующие атрибуты больше не вызывают [`AttributeError`](https://python-all.ru/3.12/library/exceptions.html#AttributeError).523524Изменено в версии 3.4: Атрибут `__wrapped__` теперь всегда ссылается на обёрнутую функцию, даже если эта функция определяла атрибут `__wrapped__`. (см. [bpo-17482](https://python-all.ru/3.12/library/functools.html))525526Изменено в версии 3.12: Атрибут [`__type_params__`](https://python-all.ru/3.12/reference/datamodel.html#function.__type_params__) теперь копируется по умолчанию.527528#### `@functools.wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)`529530Это удобная функция для вызова [`update_wrapper()`](https://python-all.ru/3.12/library/functools.html#functools.update_wrapper) в качестве декоратора функции при определении функции-обёртки. Она эквивалентна `partial(update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated)`. Например:531532```python533>>> from functools import wraps534>>> def my_decorator(f):535...     @wraps(f)536...     def wrapper(*args, **kwds):537...         print('Calling decorated function')538...         return f(*args, **kwds)539...     return wrapper540...541>>> @my_decorator542... def example():543...     """докстринг"""544...     print('Called example function')545...546>>> example()547Calling decorated function548Called example function549>>> example.__name__550'example'551>>> example.__doc__552'Docstring'553```554555Без использования этой фабрики декораторов имя примера функции было бы `'wrapper'`, а строка документации исходной `example()` была бы потеряна.556557## [`partial`](https://python-all.ru/3.12/library/functools.html#functools.partial) Объекты558559Объекты [`partial`](https://python-all.ru/3.12/library/functools.html#functools.partial) – это вызываемые объекты, создаваемые [`partial()`](https://python-all.ru/3.12/library/functools.html#functools.partial). Они имеют три атрибута только для чтения:560561#### `partial.func`562563Вызываемый объект или функция. Вызовы объекта [`partial`](https://python-all.ru/3.12/library/functools.html#functools.partial) будут перенаправлены в [`func`](https://python-all.ru/3.12/library/functools.html#functools.partial.func) с новыми аргументами и ключевыми словами.564565#### `partial.args`566567Крайние левые позиционные аргументы, которые будут добавлены перед позиционными аргументами, переданными при вызове объекта [`partial`](https://python-all.ru/3.12/library/functools.html#functools.partial).568569#### `partial.keywords`570571Ключевые аргументы, которые будут предоставлены при вызове объекта [`partial`](https://python-all.ru/3.12/library/functools.html#functools.partial).572573[`partial`](https://python-all.ru/3.12/library/functools.html#functools.partial) объекты похожи на [объекты функций](https://python-all.ru/3.12/reference/datamodel.html#user-defined-funcs) тем, что они вызываемы, допускают слабые ссылки и могут иметь атрибуты. Есть несколько важных отличий. Например, атрибуты [`__name__`](https://python-all.ru/3.12/reference/datamodel.html#function.__name__) и [`function.__doc__`](https://python-all.ru/3.12/reference/datamodel.html#function.__doc__) не создаются автоматически. Кроме того, объекты [`partial`](https://python-all.ru/3.12/library/functools.html#functools.partial), определённые в классах, ведут себя как статические методы и не превращаются в связанные методы при поиске атрибута экземпляра.574