Содержание страницы
functools – Функции высшего порядка и операции над вызываемыми объектами¶functools – Higher-order functions and operations on callable objects
Исходный код: 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) # результат ранее не кэшировался, выполняется 11 рекурсивных вызовов 3628800 >>> factorial(5) # просто возвращает закешированное значение результата 120 >>> factorial(12) # делает два новых рекурсивных вызова, остальные 10 закешированы 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)) # порядок сортировки с учётом локали
Примеры сортировки и краткое руководство см. в Sorting HOW TO.
Новое в версии 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)будут считаться разными вызовами с разными результатами.Обёрнутая функция оснащается
cache_parameters()функцией, которая возвращает новыйdict, отображающий значения maxsize и typed. Это только для информационных целей. Изменение значений не влияет на поведение.Чтобы оценить эффективность кеша и настроить параметр maxsize, обёрнутая функция дополняется функцией
cache_info(), которая возвращает именованный кортеж, показывающий hits (попадания), misses (промахи), maxsize и currsize. В многопоточной среде значения попаданий и промахов являются приблизительными.Декоратор также предоставляет функцию
cache_clear()для очистки или инвалидации кэша.Исходная базовая функция доступна через атрибут
__wrapped__. Это полезно для интроспекции, обхода кэша или повторной обёртки функции другим кэшем.LRU-кэш (вытесняющий давно неиспользовавшиеся элементы) работает лучше всего, когда самые недавние вызовы являются наилучшими предикторами будущих вызовов (например, самые популярные статьи на новостном сервере обычно меняются каждый день). Ограничение размера кэша гарантирует, что кэш не будет расти бесконечно в долго работающих процессах, таких как веб-серверы.
В целом, 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()))
Примечание
Хотя этот декоратор упрощает создание хорошо ведущих себя полностью упорядоченных типов, он действительно приводит к более медленному выполнению и более сложным трассировкам стека для производных методов сравнения. Если бенчмаркинг производительности показывает, что это является узким местом для конкретного приложения, реализация всех шести методов расширенного сравнения вместо этого, вероятно, даст лёгкий прирост скорости.
Новое в версии 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__делегируются базовому дескриптору, а в качестве результата возвращается подходящий частичный объект.Когда 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)
Для кода, не использующего аннотации типов, подходящий тип можно передать явно самому декоратору:
>>> @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
Если для конкретного типа нет зарегистрированной реализации, используется порядок разрешения методов (MRO) этого типа для поиска более общей реализации. Исходная функция, декорированная
@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) # примечание: реализация по умолчанию <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.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__и__doc__, строку документации) иWRAPPER_UPDATES(которая обновляет__dict__функции-обёртки, т.е. словарь экземпляра).Чтобы обеспечить доступ к исходной функции для интроспекции и других целей (например, обхода кэширующего декоратора такого как
lru_cache()), эта функция автоматически добавляет атрибут__wrapped__к обёртке, который ссылается на обёрнутую функцию.Основное назначение этой функции – использование в decorator-функциях, которые оборачивают декорированную функцию и возвращают обёртку. Если функция-обёртка не обновляется, метаданные возвращаемой функции будут отражать определение обёртки, а не исходной функции, что обычно не очень полезно.
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(): ... """докстринг""" ... print('Called example function') ... >>> example() Calling decorated function Called example function >>> example.__name__ 'example' >>> example.__doc__ 'Docstring'
Без использования этой фабрики декораторов имя примера функции было бы
'wrapper', а строка документации исходнойexample()была бы потеряна.
partial Объекты¶partial Objects
Объекты partial – это вызываемые объекты, создаваемые partial(). Они имеют три атрибута только для чтения:
-
partial.func¶ Вызываемый объект или функция. Вызовы объекта
partialбудут перенаправлены вfuncс новыми аргументами и ключевыми словами.
-
partial.args¶ Крайние левые позиционные аргументы, которые будут добавлены перед позиционными аргументами, переданными при вызове объекта
partial.
Объекты partial похожи на объекты function тем, что они вызываемы, могут быть слабыми ссылками и могут иметь атрибуты.
Есть несколько важных отличий.
Например, атрибуты __name__ и __doc__ не создаются автоматически.
Кроме того, объекты partial, определённые в классах,
ведут себя как статические методы и не превращаются в связанные методы при поиске атрибута экземпляра.