Spec-Zone.ru › Python 3.9

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

Исходный код: 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)      # no previously cached result, makes 11 recursive calls
3628800
>>> factorial(5)       # just looks up cached value result
120
>>> factorial(12)      # makes two new recursive calls, the other 10 are cached
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))  # 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 установлено в истину, аргументы функции разных типов будут кешироваться отдельно. Например, f(3) и f(3.0) будут обрабатываться как различные вызовы с различными результатами.

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

Для измерения эффективности кеша и настройки параметра 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 = '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()

END_OF_DOCUMENT_MARKER
@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__ делегируются базовому дескриптору, и возвращается соответствующий объект partial как результат.

Если 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])

Применяет функцию 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. При определении функции с помощью @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

Если реализация для определённого типа не зарегистрирована, используется порядок разрешения методов для поиска более обобщённой реализации. Исходная функция, помеченная декоратором @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)    # 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. При определении функции с помощью @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__ к обёртке, который ссылается на обёрнутую функцию.

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

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

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

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

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

Changed in version 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, определённые в классах, ведут себя как статические методы и не преобразуются в связанные методы во время поиска атрибутов экземпляра.

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

Spec-Zone.ru

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