Spec-Zone.ru › Python 3.11

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 ложно, реализация обычно будет считать их эквивалентными вызовами и кэшировать только один результат. (Некоторые типы, такие как 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 = '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 newfunc

Объект partial() используется для частичного применения функций, что позволяет «заморозить» часть аргументов и/или ключевых слов функции, создавая новый объект с упрощенной сигнатурой. Например, partial() можно использовать для создания вызываемого объекта, который ведет себя как функция int(), где аргумент base имеет значение по умолчанию два:

>>> from functools import partial
>>> basetwo = partial(int, base=2)
>>> basetwo.__doc__ = 'Convert base 2 string to an int.'
>>> basetwo('10010')
18
class functools.partialmethod(func, /, *args, **keywords)

Возвращает новый partialmethod дескриптор, который ведет себя как partial, за исключением того, что он предназначен для использования в качестве определения метода, а не для непосредственного вызова.

func должно быть дескриптором или вызываемым объектом (объекты, которые являются и тем, и другим, как обычные функции, обрабатываются как дескрипторы).

Когда func является дескриптором (таким как обычная Python-функция, classmethod(), staticmethod(), abstractmethod() или другой экземпляр partialmethod), вызовы к __get__ делегируются основному дескриптору, и соответствующий объект partial возвращается в качестве результата.

Когда func является вызываемым объектом, который не является дескриптором, динамически создается соответствующий связанный метод. Это ведет себя как обычная Python-функция при использовании в качестве метода: аргумент self вставляется как первый позиционный аргумент, даже перед аргументами args и keywords, переданными в конструктор partialmethod.

Пример:

>>> class Cell:
...     def __init__(self):
...         self._alive = False
...     @property
...     def alive(self):
...         return self._alive
...     def set_state(self, state):
...         self._alive = bool(state)
...     set_alive = partialmethod(set_state, True)
...     set_dead = partialmethod(set_state, False)
...
>>> c = Cell()
>>> c.alive
False
>>> c.set_alive()
>>> c.alive
True

Новое в версии 3.4.

functools.reduce(function, iterable[, initializer])

Применяет функцию function с двумя аргументами кумулятивно к элементам iterable слева направо, чтобы свести iterable к одному значению. Например, reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) вычисляет ((((1+2)+3)+4)+5). Левый аргумент, x, является накопленным значением, а правый аргумент, y, является обновляемым значением из iterable. Если присутствует необязательный initializer, он помещается перед элементами iterable в вычислении и служит значением по умолчанию, когда iterable пустой. Если initializer не задан и iterable содержит только один элемент, возвращается первый элемент.

Приблизительно эквивалентно:

def reduce(function, iterable, initializer=None):
    it = iter(iterable)
    if initializer is None:
        value = next(it)
    else:
        value = initializer
    for element in it:
        value = function(value, element)
    return value

См. itertools.accumulate() для итератора, который возвращает все промежуточные значения.

@functools.singledispatch

Преобразует функцию в однодиспетчерскую обобщенную функцию.

Для определения обобщенной функции используйте декоратор @singledispatch. При определении функции с помощью @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), чтобы она выглядела как функция, которую она обертывает (wrapped). Необязательные аргументы — это кортежи, которые указывают, какие атрибуты исходной функции напрямую присваиваются соответствующим атрибутам функции-обёртки, и какие атрибуты функции-обёртки обновляются соответствующими атрибутами исходной функции. Значения по умолчанию для этих аргументов — константы WRAPPER_ASSIGNMENTS (которая присваивает функции-обёртке __module__, __name__, __qualname__, __annotations__ и __doc__, строку документации) и WRAPPER_UPDATES (которая обновляет __dict__ функции-обёртки, то есть словарь экземпляра).

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

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

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

Новая в версии 3.2: Автоматическое добавление атрибута __wrapped__.

Новая в версии 3.2: Копирование атрибута __annotations__ по умолчанию.

Изменено в версии 3.2: Отсутствующие атрибуты больше не вызывают AttributeError.

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

@functools.wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)

Это удобная функция для вызова update_wrapper() как декоратора функции при определении функции-обёртки. Она эквивалентна partial(update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated). Например:

>>> from functools import wraps
>>> def my_decorator(f):
...     @wraps(f)
...     def wrapper(*args, **kwds):
...         print('Calling decorated function')
...         return f(*args, **kwds)
...     return wrapper
...
>>> @my_decorator
... def example():
...     """Docstring"""
...     print('Called example function')
...
>>> example()
Calling decorated function
Called example function
>>> example.__name__
'example'
>>> example.__doc__
'Docstring'

Без использования этого фабричного декоратора, имя функции-примера было бы 'wrapper', и строка документации исходной example() была бы потеряна.

END_OF_DOCUMENT_MARKER

Частичные объекты

partial объекты являются вызываемыми объектами, созданными с помощью partial(). У них есть три только для чтения атрибута:

partial.func

Вызываемый объект или функция. Вызовы объекта partial будут перенаправлены объекту func с новыми аргументами и ключевыми словами.

partial.args

Левосторонние позиционные аргументы, которые будут добавлены перед позиционными аргументами, предоставленными при вызове объекта partial.

partial.keywords

Ключевые аргументы, которые будут предоставлены при вызове объекта partial.

partial объекты подобны объектам function, так как они вызываемые, слабые ссылки на них разрешены, и у них могут быть атрибуты. Однако есть некоторые важные различия. Например, атрибуты __name__ и __doc__ не создаются автоматически. Кроме того, объекты partial, определенные в классах, ведут себя как статические методы и не преобразуются в связанные методы во время поиска атрибутов экземпляров.

© 2001–2023 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.11/library/functools.html

Spec-Zone.ru

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