Перейти к содержимому

functools – Функции высшего порядка и операции над вызываемыми объектами

Исходный код: Lib/functools.py


Модуль functools предназначен для функций высшего порядка: функций, которые принимают другие функции в качестве аргументов или возвращают их. В общем случае любой вызываемый объект может рассматриваться как функция для целей этого модуля.

Модуль functools определяет следующие функции:

@functools.cache(user_function)

Простой легковесный кэш функций без ограничения размера. Иногда называется “memoize”.

Возвращает то же самое, что и lru_cache(maxsize=None), создавая тонкую обёртку вокруг поиска по словарю аргументов функции. Поскольку ему никогда не требуется удалять старые значения, он меньше и быстрее, чем lru_cache() с ограничением размера.

Например:

python
@cache
def factorial(n):
    return n * factorial(n-1) if n else 1

>>> factorial(10)   # нет ранее кэшированного результата, делает 11 рекурсивных вызовов
3628800
>>> factorial(5)    # нет новых вызовов, просто возвращает кэшированный результат
120
>>> factorial(12)   # два новых рекурсивных вызова, factorial(10) кэширован
479001600

Кэш является потокобезопасным, поэтому обёрнутую функцию можно использовать в нескольких потоках. Это означает, что базовая структура данных останется согласованной при конкурентных обновлениях.

Обёрнутая функция может быть вызвана более одного раза, если другой поток совершает дополнительный вызов до того, как первоначальный вызов будет завершён и закэширован.

Однократный вызов не гарантируется, поскольку блокировки не удерживаются во время вызова функции. Потенциально другой вызов с теми же аргументами может произойти, пока первый вызов ещё выполняется.

Добавлено в версии 3.9.

@functools.cached_property(func)

Преобразует метод класса в свойство, значение которого вычисляется один раз, а затем кэшируется как обычный атрибут на всё время жизни экземпляра. Похоже на property(), но с добавлением кэширования. Полезно для дорогостоящих вычисляемых свойств экземпляров, которые в остальном являются эффективно неизменяемыми.

Пример:

python
class DataSet:

    def __init__(self, sequence_of_numbers):
        self._data = tuple(sequence_of_numbers)

    @cached_property
    def stdev(self):
        return statistics.stdev(self._data)

Механика cached_property() несколько отличается от property(). Обычное свойство блокирует запись атрибута, если не определён сеттер. Напротив, cached_property разрешает запись.

Декоратор cached_property выполняется только при обращениях и только когда атрибут с тем же именем не существует. Когда он выполняется, cached_property записывает значение в атрибут с тем же именем. Последующие чтения и записи атрибута имеют приоритет над методом cached_property, и он работает как обычный атрибут.

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

cached_property не предотвращает возможное состояние гонки при многопоточном использовании. Функция-геттер может выполниться более одного раза для одного и того же экземпляра, причём последний запуск установит кэшированное значение. Если кэшированное свойство идемпотентно или иным образом не вредоносно при многократном выполнении для одного экземпляра, это нормально. Если требуется синхронизация, реализуйте необходимую блокировку внутри декорированной функции-геттера или вокруг доступа к кэшированному свойству.

Обратите внимание, этот декоратор мешает работе PEP 412 словарей с разделяемыми ключами. Это означает, что словари экземпляров могут занимать больше места, чем обычно.

Кроме того, этот декоратор требует, чтобы атрибут __dict__ каждого экземпляра был изменяемым отображением. Это означает, что он не будет работать с некоторыми типами, такими как метаклассы (поскольку атрибуты __dict__ у экземпляров типа являются прокси только для чтения пространства имён класса), и с теми, которые указывают __slots__ без включения __dict__ в качестве одного из определённых слотов (поскольку такие классы вообще не имеют атрибута __dict__).

Если изменяемое отображение недоступно или требуется эффективное по памяти разделение ключей, эффект, аналогичный cached_property(), можно также получить, разместив property() поверх lru_cache(). Смотрите Как кэшировать вызовы методов? для получения дополнительных сведений о том, чем это отличается от cached_property().

Добавлено в версии 3.8.

Изменено в версии 3.12: До Python 3.12 cached_property включал недокументированную блокировку, чтобы гарантировать, что при многопоточном использовании функция-геттер выполняется только один раз для каждого экземпляра. Однако блокировка была на свойство, а не на экземпляр, что могло привести к неприемлемо высокой конкуренции за блокировку. В Python 3.12+ эта блокировка удалена.

functools.cmp_to_key(func)

Преобразует функцию сравнения старого образца в функцию ключа. Используется с инструментами, которые принимают функции ключа (такими как sorted(), min(), max(), heapq.nlargest(), heapq.nsmallest(), itertools.groupby()). Эта функция в первую очередь используется как переходный инструмент для программ, переносимых с Python 2, который поддерживал использование функций сравнения.

Функция сравнения – это любой вызываемый объект, который принимает два аргумента, сравнивает их и возвращает отрицательное число для «меньше», ноль для равенства или положительное число для «больше». Функция ключа – это вызываемый объект, который принимает один аргумент и возвращает другое значение, используемое в качестве ключа сортировки.

Пример:

python
sorted(iterable, key=cmp_to_key(locale.strcoll))  # порядок сортировки с учётом локали

Примеры сортировки и краткое руководство по сортировке см. в разделе Методы сортировки.

Добавлено в версии 3.2.

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

Декоратор для обёртки функции в запоминающий (мемоизирующий) вызываемый объект, который сохраняет до maxsize последних вызовов. Это может сэкономить время, когда дорогая или привязанная к вводу-выводу функция периодически вызывается с теми же аргументами.

Кеш потокобезопасен, поэтому обёрнутую функцию можно использовать в нескольких потоках. Это означает, что нижележащая структура данных останется согласованной при конкурентных обновлениях.

Обёрнутая функция может быть вызвана более одного раза, если другой поток сделает дополнительный вызов до того, как первоначальный вызов будет завершён и закеширован.

Поскольку для кеширования результатов используется словарь, позиционные и ключевые аргументы функции должны быть хешируемыми.

Различные шаблоны аргументов могут рассматриваться как различные вызовы с отдельными записями кеша. Например, f(a=1, b=2) и f(b=2, a=1) различаются порядком ключевых аргументов и могут иметь две отдельные записи кеша.

Если указан user_function, он должен быть вызываемым. Это позволяет применять декоратор lru_cache непосредственно к пользовательской функции, оставляя maxsize со значением по умолчанию 128:

python
@lru_cache
def count_vowels(word):
    return sum(word.count(vowel) for vowel in 'AEIOUaeiou')

Если maxsize установлен в None, функция LRU отключается, и кеш может расти без ограничений.

Если typed установлен в true, аргументы функции разных типов будут кешироваться отдельно. Если typed равен false, реализация обычно будет считать их эквивалентными вызовами и кешировать только один результат. (Некоторые типы, такие как str и int, могут кешироваться отдельно, даже когда typed равен false.)

Примечание: специфичность типа применяется только к непосредственным аргументам функции, а не к их содержимому. Скалярные аргументы Decimal(42) и Fraction(42) рассматриваются как различные вызовы с различными результатами. Напротив, кортежные аргументы ('answer', Decimal(42)) и ('answer', Fraction(42)) рассматриваются как эквивалентные.

Обёрнутая функция оснащается функцией cache_parameters(), которая возвращает новый объект dict, показывающий значения maxsize и typed. Это только для информационных целей. Изменение значений не имеет эффекта.

Чтобы помочь измерить эффективность кеша и настроить параметр maxsize, обёрнутая функция оснащается функцией cache_info(), которая возвращает именованный кортеж, показывающий hits, misses, maxsize и currsize.

Декоратор также предоставляет функцию cache_clear() для очистки или инвалидации кеша.

Исходная нижележащая функция доступна через атрибут __wrapped__. Это полезно для интроспекции, для обхода кеша или для повторной обёртки функции другим кешем.

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

Если метод кешируется, аргумент экземпляра self включается в кеш. См. Как кешировать вызовы методов?

LRU (наименее недавно использовавшийся) кеш работает лучше всего, когда самые последние вызовы являются лучшими предикторами предстоящих вызовов (например, самые популярные статьи на новостном сервере обычно меняются каждый день). Ограничение размера кеша гарантирует, что кеш не будет расти бесконтрольно в долго работающих процессах, таких как веб-серверы.

В целом, LRU-кеш следует использовать только тогда, когда нужно повторно использовать ранее вычисленные значения. Соответственно, не имеет смысла кешировать функции с побочными эффектами, функции, которые должны создавать различные изменяемые объекты при каждом вызове (такие как генераторы и асинхронные функции), или нечистые функции, такие как ⟦?⟧time()⟦?⟧ или ⟦?⟧random()⟦?⟧.

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

python
@lru_cache(maxsize=32)
def get_pep(num):
    'Retrieve text of a Python Enhancement Proposal'
    resource = f'https://peps.python.org/pep-{num:04d}'
    try:
        with urllib.request.urlopen(resource) as s:
            return s.read()
    except urllib.error.HTTPError:
        return 'Not Found'

>>> for n in 8, 290, 308, 320, 8, 218, 320, 279, 289, 320, 9991:
...     pep = get_pep(n)
...     print(n, len(pep))

>>> get_pep.cache_info()
CacheInfo(hits=3, misses=8, maxsize=32, currsize=8)

Пример эффективного вычисления чисел Фибоначчи с использованием кеша для реализации техники динамического программирования:

python
@lru_cache(maxsize=None)
def fib(n):
    if n < 2:
        return n
    return fib(n-1) + fib(n-2)

>>> [fib(n) for n in range(16)]
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610]

>>> fib.cache_info()
CacheInfo(hits=28, misses=16, maxsize=None, currsize=16)

Добавлено в версии 3.2.

Изменено в версии 3.3: Добавлена опция typed.

Изменено в версии 3.8: Добавлена опция user_function.

Изменено в версии 3.9: Добавлена функция cache_parameters()

@functools.total_ordering

Если класс определяет один или несколько методов упорядочения с помощью расширенных сравнений (rich comparison), этот декоратор класса предоставляет остальные. Это упрощает усилия по заданию всех возможных операций расширенного сравнения:

Класс должен определять один из методов __lt__(), __le__(), __gt__() или __ge__(). Кроме того, класс должен предоставлять метод __eq__().

Например:

python
@total_ordering
class Student:
    def _is_valid_operand(self, other):
        return (hasattr(other, "lastname") and
                hasattr(other, "firstname"))
    def __eq__(self, other):
        if not self._is_valid_operand(other):
            return NotImplemented
        return ((self.lastname.lower(), self.firstname.lower()) ==
                (other.lastname.lower(), other.firstname.lower()))
    def __lt__(self, other):
        if not self._is_valid_operand(other):
            return NotImplemented
        return ((self.lastname.lower(), self.firstname.lower()) <
                (other.lastname.lower(), other.firstname.lower()))

Примечание

Хотя этот декоратор упрощает создание хорошо ведущих себя типов с полным порядком, он действительно имеет ценой более медленное выполнение и более сложные трассировки стека для производных методов сравнения. Если бенчмарки производительности показывают, что это является узким местом для данного приложения, то реализация всех шести методов расширенного сравнения вместо этого, вероятно, даст лёгкое ускорение.

Примечание

Этот декоратор не пытается переопределить методы, объявленные в классе или его суперклассах. Это означает, что если суперкласс определяет оператор сравнения, total_ordering не будет реализовывать его снова, даже если исходный метод является абстрактным.

Добавлено в версии 3.2.

Изменено в версии 3.4: Теперь поддерживается возврат NotImplemented из нижележащей функции сравнения для нераспознанных типов.

functools.Placeholder

Cинглтон-объект, используемый как сторожевое значение для резервирования места для позиционных аргументов при вызове partial() и partialmethod().

Добавлено в версии 3.14.

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

Возвращает новый объект partial, который при вызове ведёт себя как func, вызванная с позиционными аргументами args и ключевыми аргументами keywords. Если при вызове передаются дополнительные аргументы, они добавляются к args. Если передаются дополнительные ключевые аргументы, они расширяют и переопределяют keywords. Приблизительно эквивалентно:

python
def partial(func, /, *args, **keywords):
    def newfunc(*more_args, **more_keywords):
        return func(*args, *more_args, **(keywords | more_keywords))
    newfunc.func = func
    newfunc.args = args
    newfunc.keywords = keywords
    return newfunc

Функция partial() используется для частичного применения функции, которая «замораживает» некоторую часть аргументов и/или ключевых слов функции, создавая новый объект с упрощённой сигнатурой. Например, partial() можно использовать для создания вызываемого объекта, ведущего себя как функция int(), где аргумент base по умолчанию равен 2:

python
>>> basetwo = partial(int, base=2)
>>> basetwo.__doc__ = 'Convert base 2 string to an int.'
>>> basetwo('10010')
18

Если в args присутствуют сторожевые значения Placeholder, они будут заполнены в первую очередь при вызове partial(). Это позволяет предварительно заполнить любой позиционный аргумент с помощью вызова partial(); без Placeholder можно предварительно заполнить только выбранное количество начальных позиционных аргументов.

Если присутствуют какие-либо сторожевые значения Placeholder, все они должны быть заполнены во время вызова:

python
>>> say_to_world = partial(print, Placeholder, Placeholder, "world!")
>>> say_to_world('Hello', 'dear')
Hello dear world!

Вызов say_to_world('Hello') вызывает исключение TypeError, потому что передан только один позиционный аргумент, а есть два заполнителя, которые необходимо заполнить.

Если partial() применяется к существующему объекту partial(), sentinel-значения Placeholder входного объекта заполняются новыми позиционными аргументами. Заполнитель можно сохранить, вставив новый sentinel Placeholder на место, занимаемое предыдущим Placeholder:

python
>>> from functools import partial, Placeholder as _
>>> remove = partial(str.replace, _, _, '')
>>> message = 'Hello, dear dear world!'
>>> remove(message, ' dear')
'Hello, world!'
>>> remove_dear = partial(remove, _, ' dear')
>>> remove_dear(message)
'Hello, world!'
>>> remove_first_dear = partial(remove_dear, _, 1)
>>> remove_first_dear(message)
'Hello, dear world!'

Placeholder нельзя передать в partial() как ключевой аргумент.

Изменено в версии 3.14: Добавлена поддержка Placeholder в позиционных аргументах.

class functools.partialmethod(func, /, *args, **keywords)

Возвращает новый дескриптор partialmethod, который ведёт себя как partial, за исключением того, что он предназначен для использования в качестве определения метода, а не для непосредственного вызова.

func должен быть дескриптором или вызываемым объектом (объекты, являющиеся и тем и другим, как обычные функции, обрабатываются как дескрипторы).

Когда func является дескриптором (например, обычная функция Python, classmethod(), staticmethod(), abstractmethod() или другой экземпляр partialmethod), вызовы __get__ делегируются базовому дескриптору, а в качестве результата возвращается соответствующий объект partial.

Когда func является вызываемым объектом, не являющимся дескриптором, динамически создаётся соответствующий связанный метод. Он ведёт себя как обычная функция Python при использовании в качестве метода: аргумент self будет вставлен первым позиционным аргументом, даже до args и keywords, переданных конструктору partialmethod.

Пример:

python
>>> class Cell:
...     def __init__(self):
...         self._alive = False
...     @property
...     def alive(self):
...         return self._alive
...     def set_state(self, state):
...         self._alive = bool(state)
...     set_alive = partialmethod(set_state, True)
...     set_dead = partialmethod(set_state, False)
...
>>> c = Cell()
>>> c.alive
False
>>> c.set_alive()
>>> c.alive
True

Добавлено в версии 3.4.

functools.reduce(function, iterable, /[, initial])

Применяет function двух аргументов кумулятивно к элементам iterable слева направо, сводя итерируемый объект к одному значению. Например, reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) вычисляет ((((1+2)+3)+4)+5). Левый аргумент x является накопленным значением, а правый аргумент y – обновлённым значением из iterable. Если присутствует необязательный initial, он помещается перед элементами итерируемого объекта в вычислении и служит значением по умолчанию, когда итерируемый объект пуст. Если initial не задан, и iterable содержит только один элемент, возвращается первый элемент.

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

python
initial_missing = object()

def reduce(function, iterable, /, initial=initial_missing):
    it = iter(iterable)
    if initial is initial_missing:
        value = next(it)
    else:
        value = initial
    for element in it:
        value = function(value, element)
    return value

См. itertools.accumulate() для итератора, возвращающего все промежуточные значения.

Изменено в версии 3.14: initial теперь поддерживается как ключевой аргумент.

@functools.singledispatch

Преобразует функцию в single-dispatch обобщённую функцию.

Чтобы определить обобщённую функцию, декорируйте её с помощью декоратора @singledispatch. При определении функции с помощью @singledispatch обратите внимание, что диспетчеризация выполняется по типу первого аргумента:

python
>>> from functools import singledispatch
>>> @singledispatch
... def fun(arg, verbose=False):
...     if verbose:
...         print("Let me just say,", end=" ")
...     print(arg)

Чтобы добавить перегруженные реализации к функции, используйте атрибут register() обобщённой функции, который можно использовать как декоратор. Для функций с аннотациями типов декоратор автоматически выведет тип первого аргумента:

python
>>> @fun.register
... def _(arg: int, verbose=False):
...     if verbose:
...         print("Strength in numbers, eh?", end=" ")
...     print(arg)
...
>>> @fun.register
... def _(arg: list, verbose=False):
...     if verbose:
...         print("Enumerate this:")
...     for i, elem in enumerate(arg):
...         print(i, elem)

typing.Union также можно использовать:

python
>>> @fun.register
... def _(arg: int | float, verbose=False):
...     if verbose:
...         print("Strength in numbers, eh?", end=" ")
...     print(arg)
...
>>> from typing import Union
>>> @fun.register
... def _(arg: Union[list, set], verbose=False):
...     if verbose:
...         print("Enumerate this:")
...     for i, elem in enumerate(arg):
...         print(i, elem)
...

Для кода, который не использует аннотации типов, соответствующий аргумент типа можно явно передать самому декоратору:

python
>>> @fun.register(complex)
... def _(arg, verbose=False):
...     if verbose:
...         print("Better than complicated.", end=" ")
...     print(arg.real, arg.imag)
...

Для кода, который выполняет диспетчеризацию по типу коллекции (например, list), но хочет указать тип элементов коллекции (например, list[int]), тип для диспетчеризации должен быть явно передан самому декоратору, а подсказка типа – в определение функции:

python
>>> @fun.register(list)
... def _(arg: list[int], verbose=False):
...     if verbose:
...         print("Enumerate this:")
...     for i, elem in enumerate(arg):
...         print(i, elem)

Примечание

Во время выполнения функция будет диспетчеризовать по экземпляру списка независимо от типа, содержащегося в списке, т.е. [1,2,3] будет диспетчеризоваться так же, как ["foo", "bar", "baz"]. Аннотация, указанная в этом примере, предназначена только для статических проверок типов и не влияет на выполнение.

Чтобы разрешить регистрацию лямбд и уже существующих функций, атрибут register() также можно использовать в функциональной форме:

python
>>> def nothing(arg, verbose=False):
...     print("Nothing.")
...
>>> fun.register(type(None), nothing)

Атрибут register() возвращает недекорированную функцию. Это позволяет создавать стеки декораторов, pickling и модульные тесты для каждого варианта независимо:

python
>>> @fun.register(float)
... @fun.register(Decimal)
... def fun_num(arg, verbose=False):
...     if verbose:
...         print("Half of your number:", end=" ")
...     print(arg / 2)
...
>>> fun_num is fun
False

При вызове обобщённая функция выполняет диспетчеризацию по типу первого аргумента:

python
>>> fun("Hello, world.")
Hello, world.
>>> fun("test.", verbose=True)
Let me just say, test.
>>> fun(42, verbose=True)
Strength in numbers, eh? 42
>>> fun(['spam', 'spam', 'eggs', 'spam'], verbose=True)
Enumerate this:
0 spam
1 spam
2 eggs
3 spam
>>> fun(None)
Nothing.
>>> fun(1.23)
0.615

Если для конкретного типа нет зарегистрированной реализации, для поиска более общей реализации используется порядок разрешения методов. Исходная функция, декорированная с помощью @singledispatch, регистрируется для базового типа object, что означает, что она используется, если не найдена лучшая реализация.

Если реализация зарегистрирована для абстрактного базового класса, виртуальные подклассы базового класса будут диспетчеризованы к этой реализации:

python
>>> from collections.abc import Mapping
>>> @fun.register
... def _(arg: Mapping, verbose=False):
...     if verbose:
...         print("Keys & Values")
...     for key, value in arg.items():
...         print(key, "=>", value)
...
>>> fun({"a": "b"})
a => b

Чтобы проверить, какую реализацию выберет обобщённая функция для заданного типа, используйте атрибут dispatch():

python
>>> fun.dispatch(float)
<function fun_num at 0x1035a2840>
>>> fun.dispatch(dict)    # примечание: реализация по умолчанию
<function fun at 0x103fe0000>

Для доступа ко всем зарегистрированным реализациям используйте атрибут registry только для чтения:

python
>>> fun.registry.keys()
dict_keys([<class 'NoneType'>, <class 'int'>, <class 'object'>,
          <class 'decimal.Decimal'>, <class 'list'>,
          <class 'float'>])
>>> fun.registry[float]
<function fun_num at 0x1035a2840>
>>> fun.registry[object]
<function fun at 0x103fe0000>

Добавлено в версии 3.4.

Изменено в версии 3.7: Атрибут register() теперь поддерживает использование аннотаций типов.

Изменено в версии 3.11: Атрибут register() теперь поддерживает typing.Union в качестве аннотации типа.

class functools.singledispatchmethod(func)

Преобразует метод в одиночную диспетчеризацию обобщённую функцию.

Чтобы определить обобщённый метод, декорируйте его декоратором @singledispatchmethod. При определении метода с помощью @singledispatchmethod имейте в виду, что диспетчеризация выполняется по типу первого аргумента, отличного от self или cls:

python
class Negator:
    @singledispatchmethod
    def neg(self, arg):
        raise NotImplementedError("Cannot negate a")

    @neg.register
    def _(self, arg: int):
        return -arg

    @neg.register
    def _(self, arg: bool):
        return not arg

@singledispatchmethod поддерживает вложенность с другими декораторами, такими как @classmethod. Обратите внимание: чтобы обеспечить dispatcher.register, декоратор singledispatchmethod должен быть самым внешним декоратором. Ниже приведён класс Negator с методами neg, привязанными к классу, а не к экземпляру класса:

python
class Negator:
    @singledispatchmethod
    @classmethod
    def neg(cls, arg):
        raise NotImplementedError("Cannot negate a")

    @neg.register
    @classmethod
    def _(cls, arg: int):
        return -arg

    @neg.register
    @classmethod
    def _(cls, arg: bool):
        return not arg

Тот же шаблон можно использовать для других подобных декораторов: @staticmethod, @~abc.abstractmethod и других.

Добавлено в версии 3.8.

Изменено в версии 3.15: Добавлена поддержка вызываемых объектов, не являющихся дескрипторами.

functools.update_wrapper(wrapper, wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)

Обновляет функцию-обёртку, чтобы она выглядела как функция-обёрнутая. Необязательные аргументы – кортежи, задающие, какие атрибуты исходной функции назначаются непосредственно соответствующим атрибутам функции-обёртки, а какие атрибуты функции-обёртки обновляются соответствующими атрибутами исходной функции. Значения по умолчанию для этих аргументов – константы уровня модуля WRAPPER_ASSIGNMENTS (которая назначает функции-обёртке атрибуты __module__, __name__, __qualname__, __annotations__, __type_params__ и __doc__, строку документации) и WRAPPER_UPDATES (которая обновляет атрибут __dict__ функции-обёртки, т.е. словарь экземпляра).

Чтобы обеспечить доступ к исходной функции для интроспекции и других целей (например, обхода декоратора кэширования, такого как lru_cache()), эта функция автоматически добавляет обёртке атрибут __wrapped__, ссылающийся на обёрнутую функцию.

Основное назначение этой функции – функции-декораторы, которые оборачивают декорированную функцию и возвращают обёртку. Если функция-обёртка не обновлена, метаданные возвращаемой функции будут отражать определение обёртки, а не определение исходной функции, что обычно мало помогает.

update_wrapper() может использоваться с вызываемыми объектами, отличными от функций. Любые атрибуты, указанные в assigned или updated и отсутствующие у обёртываемого объекта, игнорируются (т.е. эта функция не будет пытаться установить их у функции-обёртки). AttributeError по-прежнему возбуждается, если сама функция-обёртка не имеет каких-либо атрибутов, указанных в updated.

Изменено в версии 3.2: Атрибут __wrapped__ теперь автоматически добавляется. Атрибут __annotations__ теперь копируется по умолчанию. Отсутствующие атрибуты больше не вызывают AttributeError.

Изменено в версии 3.4: Атрибут __wrapped__ теперь всегда ссылается на обёрнутую функцию, даже если эта функция определила атрибут __wrapped__. (см. bpo-17482)

Изменено в версии 3.12: Атрибут __type_params__ теперь копируется по умолчанию.

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

Это удобная функция для вызова update_wrapper() в качестве декоратора функции при определении функции-обёртки. Она эквивалентна partial(update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated). Например:

python
>>> from functools import wraps
>>> def my_decorator(f):
...     @wraps(f)
...     def wrapper(*args, **kwds):
...         print('Calling decorated function')
...         return f(*args, **kwds)
...     return wrapper
...
>>> @my_decorator
... def example():
...     """Докстрока"""
...     print('Called example function')
...
>>> example()
Calling decorated function
Called example function
>>> example.__name__
'example'
>>> example.__doc__
'Docstring'

Без использования этой фабрики декораторов имя примера функции было бы 'wrapper', а строка документации исходной example() была бы потеряна.

partial Объекты

Объекты partial – это вызываемые объекты, создаваемые с помощью partial(). Они имеют три атрибута только для чтения:

partial.func

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

partial.args

Самые левые позиционные аргументы, которые будут добавлены перед позиционными аргументами, передаваемыми при вызове объекта partial.

partial.keywords

Ключевые аргументы, которые будут переданы при вызове объекта partial.

Объекты partial похожи на объекты функций тем, что они вызываемы, могут быть слабыми ссылками и иметь атрибуты. Есть некоторые важные отличия. Например, атрибуты __name__ и __doc__ не создаются автоматически.