Spec-Zone.ru › Python 3.8

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))  # 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):
    sentence = sentence.casefold()
    return sum(sentence.count(vowel) for vowel in 'aeiou')

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

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

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

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

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

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

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

Пример кэша 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.

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

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

>>> 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)    # 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() поддерживает использование аннотаций типов.

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

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

partial.args

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

partial.keywords

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

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

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

Spec-Zone.ru

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