Документация Python неофициальный перевод
Содержание страницы

functools – Функции высшего порядка и операции над вызываемыми объектамиfunctools – Higher-order functions and operations on callable objects

Исходный код: Lib/functools.py


Модуль functools предназначен для функций высшего порядка: функций, которые действуют на или возвращают другие функции. В общем случае любой вызываемый объект может рассматриваться как функция для целей этого модуля.

Модуль functools определяет следующие функции:

@functools.cache(user_function)

Простой легковесный кэш функций без ограничения размера. Иногда называется «мемоизация».

Возвращает то же, что и lru_cache(maxsize=None), создавая тонкую обёртку вокруг поиска по словарю для аргументов функции. Поскольку ему никогда не нужно вытеснять старые значения, он меньше и быстрее, чем lru_cache() с ограничением размера.

Например:

@cache
def factorial(n):
    return n * factorial(n-1) if n else 1

>>> factorial(10)      # результат ранее не кэшировался, выполняется 11 рекурсивных вызовов
3628800
>>> factorial(5)       # просто возвращает закешированное значение результата
120
>>> factorial(12)      # делает два новых рекурсивных вызова, остальные 10 закешированы
479001600

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

@functools.cached_property(func)

Преобразует метод класса в свойство, значение которого вычисляется один раз и затем кэшируется как обычный атрибут на всё время существования экземпляра. Аналогично property(), но с добавлением кэширования. Полезно для дорогостоящих вычисляемых свойств экземпляров, которые в остальном практически неизменны.

Пример:

class DataSet:

    def __init__(self, sequence_of_numbers):
        self._data = tuple(sequence_of_numbers)

    @cached_property
    def stdev(self):
        return statistics.stdev(self._data)

Механика cached_property() несколько отличается от property(). Обычное свойство блокирует запись атрибута, если не определён сеттер. Напротив, cached_property разрешает запись.

Декоратор cached_property выполняется только при поиске атрибута и только когда атрибут с таким именем не существует. Когда он выполняется, cached_property записывает значение в атрибут с тем же именем. Последующие чтения и записи атрибута имеют приоритет над методом cached_property, и он работает как обычный атрибут.

Кэшированное значение можно очистить, удалив атрибут. Это позволяет методу cached_property выполниться снова.

Примечание: этот декоратор мешает работе PEP 412 словарей с разделением ключей. Это означает, что словари экземпляров могут занимать больше места, чем обычно.

Кроме того, этот декоратор требует, чтобы атрибут __dict__ на каждом экземпляре был изменяемым отображением. Это означает, что он не будет работать с некоторыми типами, такими как метаклассы (поскольку атрибуты __dict__ на экземплярах типа являются прокси только для чтения пространства имён класса), и теми, которые указывают __slots__ без включения __dict__ в качестве одного из определённых слотов (так как такие классы вообще не предоставляют атрибут __dict__).

Если изменяемое отображение недоступно или требуется экономное по памяти совместное использование ключей, эффект, подобный cached_property(), можно получить, разместив property() поверх cache():

class DataSet:
    def __init__(self, sequence_of_numbers):
        self._data = sequence_of_numbers

    @property
    @cache
    def stdev(self):
        return statistics.stdev(self._data)

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

functools.cmp_to_key(func)

Преобразует старую функцию сравнения в функцию ключа. Используется с инструментами, которые принимают функции ключа (такими как sorted(), min(), max(), heapq.nlargest(), heapq.nsmallest(), itertools.groupby()). Эта функция в первую очередь используется как инструмент перехода для программ, переносимых с Python 2, который поддерживал использование функций сравнения.

Функция сравнения – это любой вызываемый объект, который принимает два аргумента, сравнивает их и возвращает отрицательное число, если первый меньше второго, ноль при равенстве или положительное число, если первый больше второго. Функция ключа – это вызываемый объект, который принимает один аргумент и возвращает другое значение, используемое в качестве ключа сортировки.

Пример:

sorted(iterable, key=cmp_to_key(locale.strcoll))  # порядок сортировки с учётом локали

Примеры сортировки и краткое руководство см. в Sorting HOW TO.

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

@functools.lru_cache(user_function)
@functools.lru_cache(maxsize=128, typed=False)

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

Поскольку для кеширования результатов используется словарь, позиционные и именованные аргументы функции должны быть хешируемыми.

Различные наборы аргументов могут считаться разными вызовами с отдельными записями в кеше. Например, f(a=1, b=2) и f(b=2, a=1) различаются порядком именованных аргументов и могут иметь две отдельные записи в кеше.

Если указан user_function, он должен быть вызываемым. Это позволяет применить декоратор lru_cache непосредственно к пользовательской функции, оставляя maxsize со значением по умолчанию 128:

@lru_cache
def count_vowels(sentence):
    sentence = sentence.casefold()
    return sum(sentence.count(vowel) for vowel in 'aeiou')

Если maxsize установлен в None, механизм LRU отключается и кэш может расти без ограничений.

Если для typed установлено значение true, аргументы функции разных типов будут кешироваться отдельно. Например, f(3) и f(3.0) будут считаться разными вызовами с разными результатами.

Обёрнутая функция оснащается cache_parameters() функцией, которая возвращает новый dict, отображающий значения maxsize и typed. Это только для информационных целей. Изменение значений не влияет на поведение.

Чтобы оценить эффективность кеша и настроить параметр maxsize, обёрнутая функция дополняется функцией cache_info(), которая возвращает именованный кортеж, показывающий hits (попадания), misses (промахи), maxsize и currsize. В многопоточной среде значения попаданий и промахов являются приблизительными.

Декоратор также предоставляет функцию cache_clear() для очистки или инвалидации кэша.

Исходная базовая функция доступна через атрибут __wrapped__. Это полезно для интроспекции, обхода кэша или повторной обёртки функции другим кэшем.

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

В целом, LRU-кеш следует использовать только тогда, когда нужно повторно использовать ранее вычисленные значения. Соответственно, нет смысла кешировать функции с побочными эффектами, функции, которым нужно создавать различные изменяемые объекты при каждом вызове, или нечистые функции, такие как time() или random().

Пример LRU-кэша для статического веб-контента:

@lru_cache(maxsize=32)
def get_pep(num):
    'Retrieve text of a Python Enhancement Proposal'
    resource = 'https://www.python.org/dev/peps/pep-%04d/' % num
    try:
        with urllib.request.urlopen(resource) as s:
            return s.read()
    except urllib.error.HTTPError:
        return 'Not Found'

>>> for n in 8, 290, 308, 320, 8, 218, 320, 279, 289, 320, 9991:
...     pep = get_pep(n)
...     print(n, len(pep))

>>> get_pep.cache_info()
CacheInfo(hits=3, misses=8, maxsize=32, currsize=8)

Пример эффективного вычисления чисел Фибоначчи с использованием кэша для реализации техники динамического программирования:

@lru_cache(maxsize=None)
def fib(n):
    if n < 2:
        return n
    return fib(n-1) + fib(n-2)

>>> [fib(n) for n in range(16)]
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610]

>>> fib.cache_info()
CacheInfo(hits=28, misses=16, maxsize=None, currsize=16)

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

Изменено в версии 3.3: Добавлена опция typed.

Изменено в версии 3.8: Добавлена опция user_function.

Новое в версии 3.9: Добавлена функция cache_parameters()

@functools.total_ordering

Для класса, определяющего один или несколько методов расширенного сравнения, этот декоратор класса предоставляет остальные. Это упрощает задачу определения всех возможных операций расширенного сравнения:

Класс должен определять один из __lt__(), __le__(), __gt__() или __ge__(). Кроме того, класс должен предоставлять метод __eq__().

Например:

@total_ordering
class Student:
    def _is_valid_operand(self, other):
        return (hasattr(other, "lastname") and
                hasattr(other, "firstname"))
    def __eq__(self, other):
        if not self._is_valid_operand(other):
            return NotImplemented
        return ((self.lastname.lower(), self.firstname.lower()) ==
                (other.lastname.lower(), other.firstname.lower()))
    def __lt__(self, other):
        if not self._is_valid_operand(other):
            return NotImplemented
        return ((self.lastname.lower(), self.firstname.lower()) <
                (other.lastname.lower(), other.firstname.lower()))

Примечание

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

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

Изменено в версии 3.4: Теперь поддерживается возврат NotImplemented из нижележащей функции сравнения для нераспознанных типов.

functools.partial(func, /, *args, **keywords)

Возвращает новый объект partial, который при вызове будет вести себя как func, вызванная с позиционными аргументами args и именованными аргументами keywords. Если при вызове передаётся больше аргументов, они добавляются к args. Если передаются дополнительные именованные аргументы, они расширяют и переопределяют keywords. Примерно эквивалентно следующему:

def partial(func, /, *args, **keywords):
    def newfunc(*fargs, **fkeywords):
        newkeywords = {**keywords, **fkeywords}
        return func(*args, *fargs, **newkeywords)
    newfunc.func = func
    newfunc.args = args
    newfunc.keywords = keywords
    return newfunc

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

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

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

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

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

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

Пример:

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

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

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

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

Примерно эквивалентно:

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

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

@functools.singledispatch

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

Чтобы определить обобщённую функцию, декорируйте её с помощью декоратора @singledispatch. При определении функции с помощью @singledispatch учтите, что диспетчеризация происходит по типу первого аргумента:

>>> from functools import singledispatch
>>> @singledispatch
... def fun(arg, verbose=False):
...     if verbose:
...         print("Let me just say,", end=" ")
...     print(arg)

Чтобы добавить перегруженные реализации функции, используйте атрибут register() обобщённой функции, который можно использовать как декоратор. Для функций с аннотациями типов декоратор автоматически определяет тип первого аргумента:

>>> @fun.register
... def _(arg: int, verbose=False):
...     if verbose:
...         print("Strength in numbers, eh?", end=" ")
...     print(arg)
...
>>> @fun.register
... def _(arg: list, verbose=False):
...     if verbose:
...         print("Enumerate this:")
...     for i, elem in enumerate(arg):
...         print(i, elem)

Для кода, не использующего аннотации типов, подходящий тип можно передать явно самому декоратору:

>>> @fun.register(complex)
... def _(arg, verbose=False):
...     if verbose:
...         print("Better than complicated.", end=" ")
...     print(arg.real, arg.imag)
...

Чтобы можно было регистрировать лямбды и уже существующие функции, атрибут register() также можно использовать в функциональной форме:

>>> def nothing(arg, verbose=False):
...     print("Nothing.")
...
>>> fun.register(type(None), nothing)

Атрибут register() возвращает недекорированную функцию. Это позволяет накладывать декораторы, pickling и создавать модульные тесты для каждого варианта независимо:

>>> @fun.register(float)
... @fun.register(Decimal)
... def fun_num(arg, verbose=False):
...     if verbose:
...         print("Half of your number:", end=" ")
...     print(arg / 2)
...
>>> fun_num is fun
False

При вызове обобщённая функция выполняет диспетчеризацию по типу первого аргумента:

>>> fun("Hello, world.")
Hello, world.
>>> fun("test.", verbose=True)
Let me just say, test.
>>> fun(42, verbose=True)
Strength in numbers, eh? 42
>>> fun(['spam', 'spam', 'eggs', 'spam'], verbose=True)
Enumerate this:
0 spam
1 spam
2 eggs
3 spam
>>> fun(None)
Nothing.
>>> fun(1.23)
0.615

Если для конкретного типа нет зарегистрированной реализации, используется порядок разрешения методов (MRO) этого типа для поиска более общей реализации. Исходная функция, декорированная @singledispatch, зарегистрирована для базового типа object, то есть она используется, если не найдено более подходящей реализации.

Если реализация зарегистрирована для абстрактного базового класса, виртуальные подклассы этого базового класса будут диспетчеризоваться к этой реализации:

>>> from collections.abc import Mapping
>>> @fun.register
... def _(arg: Mapping, verbose=False):
...     if verbose:
...         print("Keys & Values")
...     for key, value in arg.items():
...         print(key, "=>", value)
...
>>> fun({"a": "b"})
a => b

Чтобы проверить, какую реализацию выберет обобщённая функция для заданного типа, используйте атрибут dispatch():

>>> fun.dispatch(float)
<function fun_num at 0x1035a2840>
>>> fun.dispatch(dict)    # примечание: реализация по умолчанию
<function fun at 0x103fe0000>

Чтобы получить доступ ко всем зарегистрированным реализациям, используйте атрибут registry только для чтения:

>>> fun.registry.keys()
dict_keys([<class 'NoneType'>, <class 'int'>, <class 'object'>,
          <class 'decimal.Decimal'>, <class 'list'>,
          <class 'float'>])
>>> fun.registry[float]
<function fun_num at 0x1035a2840>
>>> fun.registry[object]
<function fun at 0x103fe0000>

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

Изменено в версии 3.7: Атрибут register() теперь поддерживает использование аннотаций типов.

class functools.singledispatchmethod(func)

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

Чтобы определить обобщённый метод, декорируйте его декоратором @singledispatchmethod. При определении функции с помощью @singledispatchmethod учтите, что диспетчеризация выполняется по типу первого аргумента, отличного от self или cls:

class Negator:
    @singledispatchmethod
    def neg(self, arg):
        raise NotImplementedError("Cannot negate a")

    @neg.register
    def _(self, arg: int):
        return -arg

    @neg.register
    def _(self, arg: bool):
        return not arg

@singledispatchmethod поддерживает вложение с другими декораторами, такими как @classmethod. Обратите внимание, что для поддержки dispatcher.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__.

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

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

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

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

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

>>> from functools import wraps
>>> def my_decorator(f):
...     @wraps(f)
...     def wrapper(*args, **kwds):
...         print('Calling decorated function')
...         return f(*args, **kwds)
...     return wrapper
...
>>> @my_decorator
... def example():
...     """докстринг"""
...     print('Called example function')
...
>>> example()
Calling decorated function
Called example function
>>> example.__name__
'example'
>>> example.__doc__
'Docstring'

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

partial Объектыpartial Objects

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

partial.func

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

partial.args

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

partial.keywords

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

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