functools — Функции высшего порядка и операции над вызываемыми объектами
Исходный код: Lib/functools.py
Модуль functools предназначен для функций высшего порядка: функций, которые действуют над другими функциями или возвращают их. В целом, любой вызываемый объект может рассматриваться как функция в целях этого модуля.
Модуль functools определяет следующие функции:
-
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(maxsize=128, typed=False) -
Декоратор для обертывания функции вызываемым объектом кеширования, который сохраняет до maxsize последних вызовов. Это может сэкономить время, когда дорогостоящая или ограниченная функцией ввода-вывода периодически вызывается с одинаковыми аргументами.
Поскольку для кэширования используются словари, позиционные и ключевые аргументы функции должны быть хешируемыми.
Различные шаблоны аргументов могут рассматриваться как отдельные вызовы с различными записями в кэше. Например,
f(a=1, b=2)иf(b=2, a=1)отличаются порядком ключевых аргументов и могут иметь две отдельные записи в кэше.Если maxsize установлено в значение
None, функция LRU отключена, и кэш может расти без ограничений. Функция LRU работает лучше, когда maxsize является степенью двойки.Если typed установлено в значение true, аргументы функций разных типов будут кэшироваться отдельно. Например,
f(3)иf(3.0)будут рассматриваться как отдельные вызовы с различными результатами.Для измерения эффективности кэша и настройки параметра maxsize обернутая функция снабжается функцией
cache_info(), которая возвращает именованную кортеж, отображающий hits, misses, maxsize и currsize. В многопоточной среде значения hits и misses являются приближенными.Декоратор также предоставляет функцию
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 = 'http://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.
-
@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()))Примечание
Хотя этот декоратор упрощает создание хорошо работающих типов, обладающих полной упорядоченностью, он понижает скорость выполнения и усложняет трассировку стека для производных методов сравнения. Если производительность показывает, что это узкое место для данного приложения, реализация всех шести методов богатого сравнения, скорее всего, обеспечит повышение скорости.
Введено в версии 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.copy() newkeywords.update(fkeywords) return func(*args, *fargs, **newkeywords) newfunc.func = func newfunc.args = args newfunc.keywords = keywords return newfuncОбъект
partial()используется для частичного применения функций, что «замораживает» часть аргументов и/или ключевых слов функции, в результате чего создается новый объект с упрощенной сигнатурой. Например,partial()может быть использован для создания вызываемого объекта, который ведет себя как функцияint(), где аргумент base имеет значение по умолчанию 2:>>> 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(object): ... 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 с двумя аргументами кумулятивно к элементам sequence слева направо, чтобы свести последовательность к одному значению. Например,
reduce(lambda x, y: x+y, [1, 2, 3, 4, 5])вычисляет((((1+2)+3)+4)+5). Левый аргумент, x, является накопленным значением, а правый аргумент, y, является обновляемым значением из sequence. Если присутствует необязательный initializer, он ставится перед элементами последовательности в расчёте и служит значением по умолчанию, когда последовательность пуста. Если initializer не задан, а sequence содержит только один элемент, возвращается первый элемент.Приблизительно эквивалентно:
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
-
@functools.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()возвращает неотдекорированную функцию, что позволяет использовать стекирование декораторов, пиклирование, а также создание модульных тестов для каждой варианта независимо:>>> @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, что означает её использование, если не найдена лучшая реализация.Чтобы проверить, какую реализацию выберет обобщённая функция для данного типа, используйте атрибут
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()теперь поддерживает использование аннотаций типов.
-
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.Новая в версии 3.2: Автоматическое добавление атрибута
__wrapped__.Новая в версии 3.2: Копирование атрибута
__annotations__по умолчанию.Изменено в версии 3.2: Отсутствие атрибутов больше не вызывает
AttributeError.Изменено в версии 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–2020 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.7/library/functools.html