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(). Регулярное свойство блокирует запись атрибутов, если не определен метод записи. В отличие от этого, cached_property разрешает запись.Декоратор cached_property выполняется только при обращении и только тогда, когда атрибут с таким же именем не существует. При его выполнении cached_property записывает атрибут с таким же именем. Последующие чтение и запись атрибутов имеют приоритет над методом cached_property и работают как обычный атрибут.
Кэшированное значение можно очистить, удалив атрибут. Это позволяет методу cached_property выполнить повторный запуск.
Обратите внимание, что этот декоратор вмешивается в работу PEP 412 словарей совместного использования ключей. Это означает, что словари экземпляров могут занимать больше места, чем обычно.
Кроме того, этот декоратор требует, чтобы атрибут
__dict__каждого экземпляра был изменяемым отображением. Это означает, что он не будет работать с некоторыми типами, такими как метаклассы (поскольку атрибуты__dict__экземпляров типа являются только для чтения прокси для пространства имен класса) и теми, которые указывают__slots__без включения__dict__в качестве одного из определенных слотов (поскольку такие классы вообще не предоставляют атрибут__dict__).Если изменяемое отображение недоступно или требуется эффективное совместное использование ключей, эффект, подобный
cached_property(), можно получить, добавивproperty()поверхcache():class DataSet: def __init__(self, sequence_of_numbers): self._data = sequence_of_numbers @property @cache def stdev(self): return statistics.stdev(self._data)Новая в версии 3.8.
-
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 установлено в значение true, аргументы функции разных типов будут кэшироваться отдельно. Если 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__. Это полезно для интроспекции, для обхода кэша или для повторного обертывания функции с другим кэшем.Кеш хранит ссылки на аргументы и возвращаемые значения до тех пор, пока они не устареют в кеше или пока кеш не будет очищен.
LRU (least recently used) кеш работает лучше всего, когда самые последние вызовы являются лучшими предсказателями будущих вызовов (например, самые популярные статьи на новостном сервере имеют тенденцию меняться каждый день). Ограничение размера кеша гарантирует, что кеш не будет неограниченно расти в долгосрочных процессах, таких как веб-серверы.
В общем случае кэш LRU следует использовать только тогда, когда вы хотите повторно использовать ранее вычисленные значения. Поэтому нет смысла кешировать функции с побочными эффектами, функции, которые должны создавать отдельные изменяемые объекты при каждом вызове, или нечистые функции, такие как time() или random().
Пример LRU кеша для статического веб-контента:
@lru_cache(maxsize=32) def get_pep(num): 'Retrieve text of a Python Enhancement Proposal' resource = 'https://www.python.org/dev/peps/pep-%04d/' % num 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, который при вызове будет вести себя как 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.Если 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]) -
Применяет function с двумя аргументами кумулятивно к элементам iterable слева направо, чтобы свести iterable к одному значению. Например,
reduce(lambda x, y: x+y, [1, 2, 3, 4, 5])вычисляет((((1+2)+3)+4)+5). Левый аргумент, x, является накапливаемым значением, а правый аргумент, y, является обновляемым значением из iterable. Если присутствует необязательный initializer, он помещается перед элементами iterable в вычислении и служит значением по умолчанию, когда iterable пуст. Если initializer не задан, и iterable содержит только один элемент, возвращается этот первый элемент.Приблизительно эквивалентно:
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)Для кода, не использующего аннотации типов, соответствующий аргумент типа можно явно передать самому декоратору:
>>> @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, и создавать тестовые модули для каждого варианта независимо:>>> @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()теперь поддерживает использование аннотаций типов.
-
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.register,singledispatchmethodдолжен быть внешним декоратором. Вот класс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), чтобы она выглядела как функция-объект (wrapped). Дополнительные аргументы — кортежи, определяющие, какие атрибуты исходной функции напрямую копируются в соответствующие атрибуты функции-обёртки, и какие атрибуты функции-обёртки обновляются соответствующими атрибутами исходной функции. Значения по умолчанию для этих аргументов — модульные константы
WRAPPER_ASSIGNMENTS(которые присваивают функции-обёртке__module__,__name__,__qualname__,__annotations__и__doc__, т.е. строку документации) иWRAPPER_UPDATES(которая обновляет__dict__, т.е. словарь экземпляра).Для доступа к исходной функции для интроспекции и других целей (например, для обхода кеширующего декоратора, такого как
lru_cache()), эта функция автоматически добавляет атрибут__wrapped__в обёртку, который ссылается на функцию, которая была обернута.Основное предполагаемое использование этой функции — в функциях-декораторах, которые оборачивают декорируемую функцию и возвращают обёртку. Если функция-обёртка не обновлена, метаданные возвращаемой функции будут отражать определение функции-обёртки, а не исходной функции, что обычно менее полезно.
update_wrapper()может использоваться с вызовами, отличными от функций. Любые атрибуты, указанные в assigned или updated, которые отсутствуют в объекте, который оборачивается, игнорируются (т.е. эта функция не будет пытаться установить их в функции-обёртке).AttributeErrorпо-прежнему возникает, если функция-обёртка сама отсутствует атрибуты, указанные в updated.New in version 3.2: Автоматическое добавление атрибута
__wrapped__.New in version 3.2: Копирование атрибута
__annotations__по умолчанию.Changed in version 3.2: Отсутствующие атрибуты больше не вызывают
AttributeError.Changed in version 3.4: Атрибут
__wrapped__теперь всегда ссылается на обернутую функцию, даже если эта функция определяла атрибут__wrapped__. (см. bpo-17482)
-
@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(). У них есть три атрибута только для чтения:
-
partial.func -
Вызываемый объект или функция. Вызовы объекта
partialбудут перенаправлены вfuncс новыми аргументами и ключевыми словами.
-
partial.args -
Позиционные аргументы, которые будут добавлены в начало позиционных аргументов, переданных в вызов объекта
partial.
-
partial.keywords -
Ключевые аргументы, которые будут переданы при вызове объекта
partial.
partial объекты похожи на function объекты тем, что они вызываемые, слабые ссылочные и могут иметь атрибуты. Есть некоторые важные различия. Например, атрибуты __name__ и __doc__ не создаются автоматически. Кроме того, partial объекты, определённые в классах, ведут себя как статические методы и не преобразуются в связанные методы во время поиска атрибутов экземпляра.
© 2001–2023 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.10/library/functools.html