Spec-Zone.ru › Python 3.12

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(). Регулярное свойство блокирует запись в атрибут, если не определен метод setter. Напротив, cached_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.

Изменено в версии 3.12: До Python 3.12, cached_property включала недокументированную блокировку, чтобы гарантировать, что в многопоточной среде функция-получатель будет запускаться только один раз на экземпляре. Однако блокировка была по свойству, а не по экземпляру, что могло привести к нежелательно высокой конкуренции за блокировку. В Python 3.12+ эта блокировка удалена.

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 установлено в значение «истина», функции аргументов разных типов будут кэшироваться отдельно. Если typed ложно, реализация обычно будет считать их эквивалентными вызовами и кэшировать только один результат. (Некоторые типы, такие как str и int, могут быть кэшированы отдельно, даже если 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 = f'https://peps.python.org/pep-{num:04d}'
    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 object, который, когда вызывается, будет вести себя как 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 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])

Применяйте функцию от двух аргументов кумулятивно к элементам итерируемого объекта слева направо, чтобы свести итерируемый объект к одному значению. Например, 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)

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, и создавать unit-тесты для каждой разновидности независимо:

>>> @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__, __type_params__, и __doc__, строка документации) и WRAPPER_UPDATES (которые обновляют словарь экземпляра функции-обёртки __dict__).

Для доступа к исходной функции для интроспекции и других целей (например, для обхода кеширующего декоратора, такого как lru_cache()), эта функция автоматически добавляет атрибут __wrapped__ к обёртке, который ссылается на обернутую функцию.

Основное предполагаемое использование этой функции — в функциях-декораторах, которые оборачивают декорируемую функцию и возвращают обёртку. Если функция-обёртка не обновляется, метаданные возвращаемой функции будут отражать определение функции-обёртки, а не исходной функции, что обычно не очень полезно.

update_wrapper() может быть использована с вызовами, отличными от функций. Любые атрибуты, указанные в assigned или updated, которые отсутствуют в обернутом объекте, игнорируются (т.е. эта функция не попытается установить их на функции-обёртке). AttributeError всё ещё возникает, если сама функция-обёртка отсутствует атрибуты, указанные в updated.

Изменено в версии 3.2: Атрибут __wrapped__ теперь автоматически добавляется. Атрибут __annotations__ теперь копируется по умолчанию. Отсутствующие атрибуты больше не вызывают AttributeError.

Изменено в версии 3.4: Атрибут __wrapped__ теперь всегда ссылается на обернутую функцию, даже если эта функция определяет атрибут __wrapped__. (см. bpo-17482)

Изменено в версии 3.12: Атрибут __type_params__ теперь копируется по умолчанию.

@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–2024 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.12/library/functools.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API