functools
— Функции и операции более высокого порядка с вызываемыми объектами¶
Исходный код: Lib/functools.py
Модуль functools
предназначен для функций более высокого порядка: функций, которые воздействуют на другие функции или возвращают их. В общем, любой вызываемый объект может рассматриваться как функция для целей этого модуля.
Модуль functools
определяет следующие функции:
- @functools.cache(user_function)¶
Простой облегченный кэш с неограниченной функцией. Иногда называется «memoize».
Возвращает то же самое, что и
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)¶
Преобразуйте функцию сравнения старого образца в key function. Используется с инструментами, которые выполняют ключевые функции (такие как
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 для самых последних вызовов. Это может сэкономить время, когда дорогостоящая функция или функция, связанная с вводом/выводом, периодически вызывается с одними и теми же аргументами.
Кэш является потокобезопасным, так что обернутую функцию можно использовать в нескольких потоках. Это означает, что базовая структура данных будет оставаться согласованной во время одновременных обновлений.
Обернутая функция может быть вызвана более одного раза, если другой поток выполнит дополнительный вызов до того, как первоначальный вызов будет завершен и кэширован.
Поскольку для кэширования результатов используется словарь, позиционные аргументы и ключевые слова функции должны быть hashable.
Различные шаблоны аргументов могут рассматриваться как отдельные вызовы с отдельными записями в кэше. Например,
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, могут кэшироваться отдельно, даже если значение typed равно false.)
Обратите внимание, что специфика типов применяется только к непосредственным аргументам функции, а не к их содержимому. Скалярные аргументы
Decimal(42)
иFraction(42)
обрабатываются как отдельные вызовы с различными результатами. Напротив, аргументы кортежа('answer', Decimal(42))
и('answer', Fraction(42))
рассматриваются как эквивалентные.Завершающая функция реализована с помощью функции
cache_parameters()
, которая возвращает новое значениеdict
, отображающее значения для maxsize и typed. Это только для информации. Изменение значений не имеет эффекта.Чтобы помочь измерить эффективность кэша и настроить параметр maxsize, функция wrapped оснащена функцией
cache_info()
, которая возвращает значение named tuple, показывающее совпадения, промахи, maxsize и currsize.Декоратор также предоставляет функцию
cache_clear()
для очистки или аннулирования кэша.Исходная базовая функция доступна через атрибут
__wrapped__
. Это полезно для самоанализа, для обхода кэша или для повторного использования функции с другим кэшем.Кэш хранит ссылки на аргументы и возвращаемые значения до тех пор, пока они не будут удалены из кэша или пока кэш не будет очищен.
Если метод кэширован, в кэш включается аргумент экземпляра
self
. Смотрите Как кэшировать вызовы методов?Параметр LRU (least recently used) cache лучше всего работает, когда самые последние звонки являются лучшими предикторами предстоящих звонков (например, самые популярные статьи на новостном сервере, как правило, меняются каждый день). Ограничение размера кэша гарантирует, что объем кэша не будет увеличиваться без ограничений для длительно выполняющихся процессов, таких как веб-серверы.
В общем, кэш 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)
Пример эффективного вычисления Fibonacci numbers с использованием кэша для реализации метода dynamic programming:
@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: Добавлена опция «Введено».
Изменено в версии 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 object, который при вызове будет вести себя как func, вызываемый с позиционными аргументами args и ключевыми аргументами keywords. Если в вызове указано больше аргументов, они добавляются к args. Если указаны дополнительные аргументы ключевого слова, они расширяют и переопределяют ключевые слова. Примерно эквивалентно:
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 должен быть descriptor или вызываемым (объекты, которые являются обоими, как и обычные функции, обрабатываются как дескрипторы).
Когда func является дескриптором (например, обычной функцией Python,
classmethod()
,staticmethod()
,abstractmethod()
или другим экземпляромpartialmethod
), вызывается__get__
делегируются базовому дескриптору, и в качестве результата возвращается соответствующий partial object.Когда 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¶
Преобразуйте функцию в single-dispatch generic function.
Чтобы определить универсальную функцию, оформите ее с помощью декоратора
@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) ...
Чтобы включить регистрацию lambdas и ранее существовавших функций, атрибут
register()
также можно использовать в функциональной форме:>>> def nothing(arg, verbose=False): ... print("Nothing.") ... >>> fun.register(type(None), nothing)
Атрибут
register()
возвращает функцию undecorated. Это позволяет использовать комбинацию декораторов,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
, что означает, что она используется, если не найдено лучшей реализации.Если реализация зарегистрирована как abstract base class, виртуальные подклассы базового класса будут отправлены в эту реализацию:
>>> 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)¶
Преобразуйте метод в single-dispatch generic function.
Чтобы определить универсальный метод, оформите его с помощью декоратора
@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__
к оболочке, который ссылается на обертываемую функцию.Основное предназначение этой функции заключается в функциях decorator, которые обертывают оформленную функцию и возвращают оболочку. Если функция-оболочка не обновляется, метаданные возвращаемой функции будут отражать определение оболочки, а не исходное определение функции, что обычно не очень полезно.
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
, определенные в классах, ведут себя как статические методы и не преобразуются в связанные методы при поиске атрибутов экземпляра.