functools — Функции высшего порядка и операции над вызываемыми объектами
Исходный код: Lib/functools.py
Модуль functools предназначен для функций высшего порядка: функций, которые действуют на другие функции или возвращают их. В общем случае любой вызываемый объект может рассматриваться как функция в контексте этого модуля.
Модуль functools определяет следующие функции:
-
@functools.cache(user_function) -
Простая, легкая кэширующая функция без ограничений по размеру. Иногда называется “мемоизацией”.
Возвращает то же самое, что и
lru_cache(maxsize=None), создавая тонкий оберточный слой над поиском в словаре по аргументам функции. Поскольку она никогда не нуждается в удалении старых значений, она меньше и быстрее, чемlru_cache()с ограничением по размеру.Например:
@cache def factorial(n): return n * factorial(n-1) if n else 1 >>> factorial(10) # no previously cached result, makes 11 recursive calls 3628800 >>> factorial(5) # just looks up cached value result 120 >>> factorial(12) # makes two new recursive calls, the other 10 are cached 479001600Кэш является потокобезопасным, поэтому обернутая функция может использоваться в нескольких потоках. Это означает, что лежащая в основе структура данных будет сохранять согласованность во время одновременных обновлений.
Возможно, что обернутая функция будет вызвана более одного раза, если другой поток выполнит дополнительный вызов до того, как начальный вызов будет завершен и кэширован.
Добавлена в версии 3.9.
-
@functools.cached_property(func) -
Преобразует метод класса в свойство, значение которого вычисляется один раз и затем кэшируется как обычное атрибут на протяжении всего жизненного цикла экземпляра. Аналогично
property(), с добавлением кэширования. Полезно для дорогостоящих вычисляемых свойств экземпляров, которые в противном случае фактически неизменяемы.Пример:
class DataSet: def __init__(self, sequence_of_numbers): self._data = tuple(sequence_of_numbers) @cached_property def stdev(self): return statistics.stdev(self._data)Механизм
cached_property()несколько отличается отproperty(). Регулярное свойство блокирует запись в атрибут, если не определен метод setter. Напротив, 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, поддерживающего использование функций сравнения.Функция сравнения — это любой вызываемый объект, который принимает два аргумента, сравнивает их и возвращает отрицательное число для меньше, ноль для равенства или положительное число для больше.
Функция ключа — это вызываемый объект, который принимает один аргумент и возвращает другое значение, которое будет использоваться в качестве ключа сортировки.
Пример:
sorted(iterable, key=cmp_to_key(locale.strcoll)) # locale-aware sort order
Примеры сортировки и краткого руководства по сортировке см. в разделе Методы сортировки.
Добавлена в версии 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:
@lru_cache def count_vowels(sentence): return sum(sentence.count(vowel) for vowel in 'AEIOUaeiou')Если maxsize установлено на
None, функция LRU отключена, и кэш может расти без ограничений.Если typed установлено в значение «истина», функции аргументов разных типов будут кэшироваться отдельно. Если typed ложно, реализация обычно будет считать их эквивалентными вызовами и кэшировать только один результат. (Некоторые типы, такие как str и int, могут быть кэшированы отдельно, даже если typed ложно.)
Обратите внимание, что специфичность типа применяется только к непосредственным аргументам функции, а не к их содержимому. Скалярные аргументы,
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 (least recently used) лучше всего работает, когда самые последние вызовы являются лучшими предсказателями будущих вызовов (например, самые популярные статьи на новостном сервере обычно меняются каждый день). Ограничение размера кэша гарантирует, что кэш не будет расти без ограничений в долгосрочных процессах, таких как веб-серверы.
В общем случае кэш LRU следует использовать только тогда, когда вы хотите повторно использовать ранее вычисленные значения. Соответственно, нет смысла кэшировать функции с побочными эффектами, функции, которые нуждаются в создании различных изменяемых объектов при каждом вызове (такие как генераторы и асинхронные функции), или нечистые функции, такие как time() или random().
Пример кэша LRU для статического веб-контента:
@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)Пример эффективного вычисления чисел Фибоначчи с использованием кэша для реализации техники динамического программирования:
@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 -
Для класса, определяющего один или несколько методов богатого сравнения, этот декоратор класса предоставляет остальные. Это упрощает задачу задания всех возможных операций богатого сравнения:
Класс должен определить один из
__lt__(),__le__(),__gt__(), или__ge__(). Кроме того, класс должен предоставить метод__eq__().Например:
@total_ordering class Student: def _is_valid_operand(self, other): return (hasattr(other, "lastname") and hasattr(other, "firstname")) def __eq__(self, other): if not self._is_valid_operand(other): return NotImplemented return ((self.lastname.lower(), self.firstname.lower()) == (other.lastname.lower(), other.firstname.lower())) def __lt__(self, other): if not self._is_valid_operand(other): return NotImplemented return ((self.lastname.lower(), self.firstname.lower()) < (other.lastname.lower(), other.firstname.lower()))Примечание
Хотя этот декоратор упрощает создание хорошо себя ведущих упорядоченных типов, он увеличивает время выполнения и усложняет трассировки стека для производных методов сравнения. Если бенчмаркинг производительности показывает, что это узкое место для данного приложения, реализация всех шести методов богатого сравнения, скорее всего, даст прирост скорости.
Примечание
Этот декоратор не пытается переопределить методы, объявленные в классе или его родительских классах. Это означает, что если родительский класс определяет оператор сравнения, total_ordering не будет его повторно реализовывать, даже если исходный метод является абстрактным.
Добавлена в версии 3.2.
Изменено в версии 3.4: Теперь поддерживается возвращение
NotImplementedиз базовой функции сравнения для типов, которые не распознаются.
-
functools.partial(func, /, *args, **keywords) -
Возвращает новый объект partial object, который, когда вызывается, будет вести себя как func, вызываемый с позиционными аргументами args и ключевыми аргументами keywords. Если передаются дополнительные аргументы, они добавляются к args. Если передаются дополнительные ключевые аргументы, они расширяют и переопределяют keywords. Примерно эквивалентно:
def partial(func, /, *args, **keywords): def newfunc(*fargs, **fkeywords): newkeywords = {**keywords, **fkeywords} return func(*args, *fargs, **newkeywords) newfunc.func = func newfunc.args = args newfunc.keywords = keywords return newfuncОбъект
partial()используется для частичного применения функций, «замораживая» часть аргументов и/или ключевых слов функции, что приводит к новому объекту с упрощенной сигнатурой. Например,partial()может быть использован для создания вызываемого объекта, который ведет себя как функцияint()с аргументом base по умолчанию, равным двум:>>> from functools import partial >>> basetwo = partial(int, base=2) >>> basetwo.__doc__ = 'Convert base 2 string to an int.' >>> basetwo('10010') 18
-
class functools.partialmethod(func, /, *args, **keywords) -
Возвращает новый описатель
partialmethod, который ведет себя какpartial, за исключением того, что он предназначен для использования в качестве определения метода, а не для непосредственного вызова.func должен быть описателем или вызываемым объектом (объекты, являющиеся и тем, и другим, такие как обычные функции, обрабатываются как описатели).
Когда func является описателем (таким как обычная Python-функция,
classmethod(),staticmethod(),abstractmethod()или другой экземплярpartialmethod), вызовы__get__делегируются базовому описателю, и соответствующий объект partial object возвращается в качестве результата.Когда func является вызываемым объектом, не являющимся описателем, динамически создаётся соответствующий связанный метод. Он ведет себя как обычная Python-функция, когда используется как метод: аргумент self будет вставлен как первый позиционный аргумент, даже перед аргументами args и keywords, переданными в конструктор
partialmethod.Пример:
>>> class Cell: ... def __init__(self): ... self._alive = False ... @property ... def alive(self): ... return self._alive ... def set_state(self, state): ... self._alive = bool(state) ... set_alive = partialmethod(set_state, True) ... set_dead = partialmethod(set_state, False) ... >>> c = Cell() >>> c.alive False >>> c.set_alive() >>> c.alive True
Добавлена в версии 3.4.
-
functools.reduce(function, iterable[, initializer]) -
Применяйте функцию от двух аргументов кумулятивно к элементам итерируемого объекта слева направо, чтобы свести итерируемый объект к одному значению. Например,
reduce(lambda x, y: x+y, [1, 2, 3, 4, 5])вычисляет((((1+2)+3)+4)+5). Левый аргумент, x, — это накопленное значение, а правый аргумент, y, — обновляемое значение из итерируемого объекта. Если необязательный инициализатор присутствует, он помещается перед элементами итерируемого объекта в расчёте и служит значением по умолчанию, когда итерируемый объект пуст. Если инициализатор не задан, а итерируемый объект содержит только один элемент, возвращается первый элемент.Приблизительно эквивалентно:
def reduce(function, iterable, initializer=None): it = iter(iterable) if initializer is None: value = next(it) else: value = initializer for element in it: value = function(value, element) return valueСм.
itertools.accumulate()для итератора, который генерирует все промежуточные значения.
-
@functools.singledispatch -
Преобразуйте функцию в функцию с единым вызовом обобщённую функцию.
Для определения обобщённой функции используйте декоратор
@singledispatch. При определении функции с помощью@singledispatch, обратите внимание, что диспетчеризация происходит по типу первого аргумента:>>> from functools import singledispatch >>> @singledispatch ... def fun(arg, verbose=False): ... if verbose: ... print("Let me just say,", end=" ") ... print(arg)Для добавления перегруженных реализаций в функцию используйте атрибут
register()обобщённой функции, который можно использовать как декоратор. Для функций с аннотациями типов декоратор будет автоматически определять тип первого аргумента:>>> @fun.register ... def _(arg: int, verbose=False): ... if verbose: ... print("Strength in numbers, eh?", end=" ") ... print(arg) ... >>> @fun.register ... def _(arg: list, verbose=False): ... if verbose: ... print("Enumerate this:") ... for i, elem in enumerate(arg): ... print(i, elem)types.UnionTypeиtyping.Unionтакже могут быть использованы:>>> @fun.register ... def _(arg: int | float, verbose=False): ... if verbose: ... print("Strength in numbers, eh?", end=" ") ... print(arg) ... >>> from typing import Union >>> @fun.register ... def _(arg: Union[list, set], verbose=False): ... if verbose: ... print("Enumerate this:") ... for i, elem in enumerate(arg): ... print(i, elem) ...Для кода, не использующего аннотации типов, соответствующий тип аргумента можно явно передать самому декоратору:
>>> @fun.register(complex) ... def _(arg, verbose=False): ... if verbose: ... print("Better than complicated.", end=" ") ... print(arg.real, arg.imag) ...Для регистрации лямбда-выражений и уже существующих функций также может быть использован атрибут
register()в функциональной форме:>>> def nothing(arg, verbose=False): ... print("Nothing.") ... >>> fun.register(type(None), nothing)Атрибут
register()возвращает функцию без декоратора. Это позволяет применять стекинг декораторов,pickling, и создавать unit-тесты для каждой разновидности независимо:>>> @fun.register(float) ... @fun.register(Decimal) ... def fun_num(arg, verbose=False): ... if verbose: ... print("Half of your number:", end=" ") ... print(arg / 2) ... >>> fun_num is fun FalseПри вызове обобщённая функция диспетчеризируется по типу первого аргумента:
>>> fun("Hello, world.") Hello, world. >>> fun("test.", verbose=True) Let me just say, test. >>> fun(42, verbose=True) Strength in numbers, eh? 42 >>> fun(['spam', 'spam', 'eggs', 'spam'], verbose=True) Enumerate this: 0 spam 1 spam 2 eggs 3 spam >>> fun(None) Nothing. >>> fun(1.23) 0.615В случае отсутствия зарегистрированной реализации для определённого типа используется порядок разрешения методов для поиска более общей реализации. Исходная функция, декорированная
@singledispatch, зарегистрирована для базового типаobject, что означает её использование, если не найдена лучшая реализация.Если реализация зарегистрирована для абстрактного базового класса, виртуальные подклассы базового класса будут диспетчеризованы к этой реализации:
>>> from collections.abc import Mapping >>> @fun.register ... def _(arg: Mapping, verbose=False): ... if verbose: ... print("Keys & Values") ... for key, value in arg.items(): ... print(key, "=>", value) ... >>> fun({"a": "b"}) a => bДля проверки реализации, которую выберет обобщённая функция для данного типа, используйте атрибут
dispatch():>>> fun.dispatch(float) <function fun_num at 0x1035a2840> >>> fun.dispatch(dict) # note: default implementation <function fun at 0x103fe0000>
Для доступа ко всем зарегистрированным реализациям используйте только для чтения атрибут
registry:>>> fun.registry.keys() dict_keys([<class 'NoneType'>, <class 'int'>, <class 'object'>, <class 'decimal.Decimal'>, <class 'list'>, <class 'float'>]) >>> fun.registry[float] <function fun_num at 0x1035a2840> >>> fun.registry[object] <function fun at 0x103fe0000>Добавлена в версии 3.4.
Изменено в версии 3.7: Атрибут
register()теперь поддерживает использование аннотаций типов.Изменено в версии 3.11: Атрибут
register()теперь поддерживаетtypes.UnionTypeиtyping.Unionкак аннотации типов.
-
class functools.singledispatchmethod(func) -
Преобразуйте метод в функцию с единым вызовом обобщённую функцию.
Для определения обобщённого метода используйте декоратор
@singledispatchmethod. При определении функции с помощью@singledispatchmethod, обратите внимание, что диспетчеризация происходит по типу первого аргумента, не являющегося self или cls:class Negator: @singledispatchmethod def neg(self, arg): raise NotImplementedError("Cannot negate a") @neg.register def _(self, arg: int): return -arg @neg.register def _(self, arg: bool): return not arg@singledispatchmethodподдерживает вложение с другими декораторами, такими как@classmethod. Обратите внимание, что дляdispatcher.registersingledispatchmethodдолжен быть внешним декоратором. Вот классNegator, с методамиnegпривязанными к классу, а не к экземпляру класса:class Negator: @singledispatchmethod @classmethod def neg(cls, arg): raise NotImplementedError("Cannot negate a") @neg.register @classmethod def _(cls, arg: int): return -arg @neg.register @classmethod def _(cls, arg: bool): return not argТакой же шаблон можно использовать для других аналогичных декораторов:
@staticmethod,@abstractmethodи другие.Добавлена в версии 3.8.
-
functools.update_wrapper(wrapper, wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES) -
Обновляет функцию-обёртку так, чтобы она выглядела как обернутая функция. Необязательные аргументы — это кортежи, которые определяют, какие атрибуты исходной функции напрямую назначаются соответствующим атрибутам функции-обёртки, и какие атрибуты функции-обёртки обновляются с соответствующими атрибутами исходной функции. Значения по умолчанию для этих аргументов — это модульные константы
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). Например:>>> from functools import wraps >>> def my_decorator(f): ... @wraps(f) ... def wrapper(*args, **kwds): ... print('Calling decorated function') ... return f(*args, **kwds) ... return wrapper ... >>> @my_decorator ... def example(): ... """Docstring""" ... print('Called example function') ... >>> example() Calling decorated function Called example function >>> example.__name__ 'example' >>> example.__doc__ 'Docstring'Без использования этой фабрики декоратора имя функции-примера было бы
'wrapper', и строка документации исходнойexample()была бы потеряна.
Частичные объекты
partial объекты являются вызываемыми объектами, созданными с помощью partial(). У них есть три только для чтения атрибута:
-
partial.func -
Вызываемый объект или функция. Вызовы объекта
partialбудут переданы объектуfuncс новыми аргументами и ключевыми словами.
-
partial.args -
Позиционные аргументы слева, которые будут добавлены перед позиционными аргументами, предоставленными при вызове объекта
partial.
-
partial.keywords -
Ключевые аргументы, которые будут переданы при вызове объекта
partial.
partial объекты подобны function объектам, так как они вызываемые, поддерживают слабые ссылки и могут иметь атрибуты. Есть некоторые важные различия. Например, атрибуты __name__ и __doc__ не создаются автоматически. Кроме того, объекты partial, определенные в классах, ведут себя как статические методы и не преобразуются в связанные методы во время поиска атрибутов экземпляра.
© 2001–2024 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.12/library/functools.html