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

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


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

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

@functools.cache(user_function)

Простой легкий неограниченный кэш функций. Иногда называется «memoize».

Возвращает то же самое, что и 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)

Преобразуйте функцию сравнения старого стиля в key function. Используется с инструментами, которые принимают ключевые функции (такие как 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):
    return sum(sentence.count(vowel) for vowel in 'AEIOUaeiou')

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

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

Обратите внимание, что спецификация типов применяется только к непосредственным аргументам функции, а не к их содержимому. Скалярные аргументы Decimal(42) и Fraction(42) рассматриваются как разные вызовы с разными результатами. Напротив, аргументы кортежа ('answer', Decimal(42)) и ('answer', Fraction(42)) рассматриваются как эквивалентные.

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

Чтобы помочь измерить эффективность кэша и настроить параметр maxsize, обернутая функция оснащена функцией cache_info(), которая возвращает named tuple, показывая hits, misses, maxsize и currsize.

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

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

Кэш сохраняет ссылки на аргументы и возвращаемые значения до тех пор, пока они не выйдут из кэша или пока кэш не будет очищен.

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

В целом, кэш 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)

Пример эффективного вычисления Fibonacci numbers с использованием кэша для реализации техники dynamic programming:

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

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

Примечание

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

Примечание

Этот декоратор не пытается переопределить методы, которые были объявлены в классе или его суперклассах. Это означает, что если суперкласс определяет оператор сравнения, total_ordering не будет реализовывать его снова, даже если исходный метод является абстрактным.

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

Изменено в версии 3.4: Теперь поддерживается возврат NotImplemented из базовой функции сравнения для нераспознанных типов.

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

Возвращает новый partial object, который при вызове будет вести себя как 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 должен быть descriptor или callable (объекты, которые являются и тем, и другим, как обычные функции, обрабатываются как дескрипторы).

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

Когда 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])

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

Преобразование функции в single-dispatch generic function.

Чтобы определить общую функцию, украсьте ее декоратором @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)
...

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

Если реализация зарегистрирована в abstract base class, виртуальные подклассы базового класса будут диспетчеризироваться к этой реализации:

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

Преобразование метода в single-dispatch generic function.

Чтобы определить общий метод, украсьте его декоратором @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__, который ссылается на обертываемую функцию.

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

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', а docstring оригинальной example() был бы потерян.

partial Объекты

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

partial.func

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

partial.args

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

partial.keywords

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

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

Вернуться на верх