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()поверхlru_cache(). Дополнительные сведения об отличиях отcached_property()см. в разделе Как кэшировать вызовы методов?.Добавлено в версии 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 задано значение false, реализация обычно считает их эквивалентными вызовами и кэширует только один результат. (Некоторые типы, например str и int, могут кэшироваться отдельно даже при значении false для 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 = 'https://peps.python.org/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 newfuncpartial()используется для частичного применения функции, при котором часть аргументов и/или именованных аргументов функции «фиксируется», в результате чего создаётся новый объект с упрощённой сигнатурой. Например,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 слева направо, сводя итерируемый объект к одному значению. Например,
reduce(lambda x, y: x+y, [1, 2, 3, 4, 5])вычисляет((((1+2)+3)+4)+5). Левый аргумент, x, — это накопленное значение, а правый аргумент, y, — обновлённое значение из iterable. Если указан необязательный аргумент initializer, он помещается перед элементами итерируемого объекта при вычислении и используется как значение по умолчанию, если итерируемый объект пуст. Если 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)Также можно использовать
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и создавать модульные тесты для каждого варианта отдельно:>>> @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.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_ASSIGNMENTS(присваивает функции-обёртке атрибуты__module__,__name__,__qualname__,__annotations__и__doc__— строку документации) иWRAPPER_UPDATES(обновляет атрибут__dict__функции-обёртки, то есть словарь экземпляра).Чтобы обеспечить доступ к исходной функции для интроспекции и других целей (например, для обхода декоратора кеширования, такого как
lru_cache()), эта функция автоматически добавляет функции-обёртке атрибут__wrapped__, ссылающийся на оборачиваемую функцию.Основное назначение этой функции — использование в функциях-декораторах, которые оборачивают декорируемую функцию и возвращают обёртку. Если функция-обёртка не обновлена, метаданные возвращённой функции будут отражать определение обёртки, а не исходной функции, что обычно мало полезно.
update_wrapper()можно использовать с вызываемыми объектами, отличными от функций. Любые атрибуты, указанные в assigned или updated и отсутствующие у оборачиваемого объекта, игнорируются (то есть эта функция не пытается установить их для функции-обёртки). ОднакоAttributeErrorвозникает, если у самой функции-обёртки отсутствует какой-либо из атрибутов, указанных в updated.Изменено в версии 3.2: Атрибут
__wrapped__теперь добавляется автоматически. Атрибут__annotations__теперь копируется по умолчанию. Отсутствующие атрибуты больше не вызывают исключение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–2024 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.11/library/functools.html