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__ атрибут на каждом сущность были изменяемым отображением (неизменный). Это означает, что он не будет работать с некоторыми типами, такими как metaclasses (поскольку __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)

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

Так как словарь - используемый к результатам кэш, позиционные и аргументы ключевой функции должны быть хэшируемым.

Отдельные шаблоны аргументов могут рассматриваться как отдельные вызовы с отдельными записями кэш. Например, f(a=1, b=2) and 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 (least recently used) cache работает лучше всего, когда самые последние звонки являются лучшими предикторами предстоящих вызовов (например, самые популярные статьи на сервере новостей имеют тенденцию меняться каждый день). Ограничение размера кэш гарантирует, что кэш не будет расти без привязки к длительным процессам, таким как веб-серверы.

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

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

@lru_cache(maxsize=32)
def get_pep(num):
    'Получить текст предложения по улучшению Python'
    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.

Изменено в версии 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()))

Примечание

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

Добавлено в версии 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 дескриптор возвращает a, который ведет себя как partial за исключением того, что он разработан, чтобы быть используемый как определением метода вместо того, чтобы быть непосредственно подлежащим выкупу.

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

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

Когда func - non-дескриптор подлежащее выкупу, соответствующий связанный метод создан динамично. Это ведет себя как обычная функция 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("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() атрибут возвращает декорированную функцию, которая включает декоратору, складывающему, пиклинг, а также создающему модульные тесты на каждый вариант независимо:

>>> @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, что означает, что она используемый, если лучшей реализации не найдено.

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

Объекты partial

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

partial.func

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

partial.args

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

partial.keywords

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

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