functools — Функции и операции высшего порядка над вызываемыми объектами

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


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

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

@functools.cached_property(func)

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

Пример:

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

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

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

Добавлено в версии 3.8.

Примечание

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

functools.cmp_to_key(func)

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

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

Пример:

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

Примеры сортировки и краткое руководство по сортировке см. в HOWTO по сортировке.

Добавлено в версии 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 установлено истинное значение, аргументы функций разных типов будут кэшироваться отдельно. Например, f(3) и f(3.0) будут обрабатываться как отдельные вызовы с разными результатами.

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

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

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

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

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

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

@lru_cache(maxsize=32)
def get_pep(num):
    'Получение текста Предложения по улучшению Python (PEP)'
    resource = 'http://www.python.org/dev/peps/pep-%04d/' % num
    try:
        with urllib.request.urlopen(resource) as s:
            return s.read()
    except urllib.error.HTTPError:
        return 'Не обнаружено'

>>> 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.

@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)

Возвращает новый частичный объект, который при вызове будет вести себя как 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(object):
...     def __init__(self):
...         self._alive = False
...     @property
...     def alive(self):
...         return self._alive
...     def set_state(self, state):
...         self._alive = bool(state)
...     set_alive = partialmethod(set_state, True)
...     set_dead = partialmethod(set_state, False)
...
>>> c = Cell()
>>> c.alive
False
>>> c.set_alive()
>>> c.alive
True

Добавлено в версии 3.4.

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

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

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

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

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

@functools.singledispatch

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

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

>>> from functools import singledispatch
>>> @singledispatch
... def fun(arg, verbose=False):
...     if verbose:
...         print("Позвольте мне просто сказать,", end=" ")
...     print(arg)

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

>>> @fun.register
... def _(arg: int, verbose=False):
...     if verbose:
...         print("Сила в количестве, а?", end=" ")
...     print(arg)
...
>>> @fun.register
... def _(arg: list, verbose=False):
...     if verbose:
...         print("Перечислить это:")
...     for i, elem in enumerate(arg):
...         print(i, elem)

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

>>> @fun.register(complex)
... def _(arg, verbose=False):
...     if verbose:
...         print("Лучше, чем заумное.", end=" ")
...     print(arg.real, arg.imag)
...

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

>>> def nothing(arg, verbose=False):
...     print("Ничего.")
...
>>> fun.register(type(None), nothing)

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

>>> @fun.register(float)
... @fun.register(Decimal)
... def fun_num(arg, verbose=False):
...     if verbose:
...         print("Половина вашего числа:", end=" ")
...     print(arg / 2)
...
>>> fun_num is fun
False

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

>>> fun("Привет, Мир.")
Привет, Мир.
>>> fun("тест.", verbose=True)
Позвольте мне просто сказать, тест.
>>> fun(42, verbose=True)
Сила в количестве, а? 42
>>> fun(['spam', 'spam', 'eggs', 'spam'], verbose=True)
Перечислить это:
0 spam
1 spam
2 eggs
3 spam
>>> fun(None)
Ничего.
>>> fun(1.23)
0.615

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

Чтобы проверить, какую реализацию общей функции выбрать для данного типа, используйте атрибут 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. Обратите внимание, что диспетчеризация происходит по типу первого аргумента non-self или non-cls, создав свою соответствующую функцию:

class Negator:
    @singledispatchmethod
    def neg(self, arg):
        raise NotImplementedError("Не может отрицать")

    @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("Не может отрицать")

    @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('Вызов декорированной функции')
...         return f(*args, **kwds)
...     return wrapper
...
>>> @my_decorator
... def example():
...     """Строка документации"""
...     print('Вызов функции примера')
...
>>> example()
Вызов декорированной функции
Вызов функции примера
>>> example.__name__
'example'
>>> example.__doc__
'Строка документации'

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

partial Объекты

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

partial.func

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

partial.args

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

partial.keywords

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

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