functools — Функции высшего порядка и операции над вызываемыми объектами
Исходный код: Lib/functools.py
Модуль functools предназначен для функций высшего порядка: функций, которые действуют над другими функциями или возвращают их. В общем случае любой вызываемый объект может рассматриваться как функция в целях этого модуля.
Модуль functools определяет следующие функции:
-
@functools.cached_property(func) -
Преобразует метод класса в свойство, значение которого вычисляется один раз и кэшируется как обычное атрибут для всего срока существования экземпляра. Аналогично
property(), с добавлением кэширования. Полезно для дорогостоящих вычисляемых свойств экземпляров, которые в противном случае являются фактически неизменяемыми.Пример:
class DataSet: def __init__(self, sequence_of_numbers): self._data = sequence_of_numbers @cached_property def stdev(self): return statistics.stdev(self._data) @cached_property def variance(self): return statistics.variance(self._data)Новое в версии 3.8.
Примечание
Этот декоратор требует, чтобы атрибут
__dict__каждого экземпляра был изменяемым отображением. Это означает, что он не будет работать с некоторыми типами, такими как метаклассы (поскольку атрибуты__dict__экземпляров типа являются только для чтения прокси для пространства имен класса) и теми, которые задают__slots__без включения__dict__в качестве одного из определенных слотов (поскольку такие классы вообще не предоставляют атрибут__dict__).
-
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): sentence = sentence.casefold() return sum(sentence.count(vowel) for vowel in 'aeiou')Если maxsize установлено в
None, функция LRU отключена, и кэш может расти без ограничений.Если typed установлено в true, аргументы функции разных типов будут кэшироваться отдельно. Например,
f(3)иf(3.0)будут обрабатываться как отдельные вызовы с различными результатами.Для измерения эффективности кэша и настройки параметра 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 = '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.
Изменено в версии 3.8: Добавлен параметр user_function.
-
@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, **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(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 от двух аргументов кумулятивно к элементам 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. Обратите внимание, что диспетчеризация происходит по типу первого аргумента, создайте свою функцию соответствующим образом:>>> 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()поддерживает использование аннотаций типов.
-
class functools.singledispatchmethod(func) -
Преобразует метод в однодиспетчерскую обобщённую функцию.
Для определения обобщённого метода используйте декоратор
@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.Новое в версии 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.func -
Вызываемый объект или функция. Вызовы объекта
partialбудут перенаправлены наfuncс новыми аргументами и ключевыми словами.
-
partial.args -
Позиционные аргументы, которые будут добавлены перед позиционными аргументами, переданными при вызове объекта
partial.
-
partial.keywords -
Ключевые аргументы, которые будут переданы при вызове объекта
partial.
partial объекты похожи на function объекты в том, что они вызываемые, поддерживают слабые ссылки и могут иметь атрибуты. Однако есть важные отличия. Например, атрибуты __name__ и __doc__ не создаются автоматически. Кроме того, объекты partial, определенные в классах, ведут себя как статические методы и не преобразуются в связанные методы во время поиска атрибутов экземпляра.
© 2001–2022 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.8/library/functools.html