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

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

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


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

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

functools.cmp_to_key(func)

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

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

Пример:

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

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

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

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

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

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

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

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

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

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

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

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

@lru_cache(maxsize=32)
def get_pep(num):
    'Retrieve text of a Python Enhancement Proposal'
    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 '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.

@functools.total_ordering

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

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

Например:

@total_ordering
class Student:
    def __eq__(self, other):
        return ((self.lastname.lower(), self.firstname.lower()) ==
                (other.lastname.lower(), other.firstname.lower()))
    def __lt__(self, other):
        return ((self.lastname.lower(), self.firstname.lower()) <
                (other.lastname.lower(), other.firstname.lower()))

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

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

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

def partial(func, *args, **keywords):
    def newfunc(*fargs, **fkeywords):
        newkeywords = keywords.copy()
        newkeywords.update(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
functools.reduce(function, iterable[, initializer])

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

Эквивалентно следующему:

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
functools.update_wrapper(wrapper, wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)

Обновляет функцию-обёртку так, чтобы она выглядела как обёрнутая функция. Необязательные аргументы – кортежи, определяющие, какие атрибуты исходной функции напрямую присваиваются соответствующим атрибутам функции-обёртки и какие атрибуты функции-обёртки обновляются соответствующими атрибутами исходной функции. Значения по умолчанию для этих аргументов – константы уровня модуля WRAPPER_ASSIGNMENTS (которая присваивает функции-обёртке атрибуты __name__, __module__, __annotations__ и __doc__, т.е. строку документации) и WRAPPER_UPDATES (которая обновляет у функции-обёртки атрибут __dict__, т.е. словарь экземпляра).

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

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

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

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

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

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

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

Это удобная функция для вызова 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() была бы потеряна.

10.2.1. partial Объектыpartial Objects

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

partial.func

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

partial.args

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

partial.keywords

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

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