Содержание страницы
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)) # порядок сортировки с учётом локали
Примеры сортировки и краткое руководство см. в Sorting HOW TO.
Новое в версии 3.2.
-
@functools.lru_cache(maxsize=128, typed=False)¶ Декоратор для обёртывания функции запоминающим вызываемым объектом, который сохраняет до maxsize последних вызовов. Это может сэкономить время, когда дорогостоящая или связанная с вводом-выводом функция периодически вызывается с одними и теми же аргументами.
Поскольку для кеширования результатов используется словарь, позиционные и именованные аргументы функции должны быть хешируемыми.
Различные наборы аргументов могут считаться разными вызовами с отдельными записями в кеше. Например, f(a=1, b=2) и f(b=2, a=1) различаются порядком именованных аргументов и могут иметь две отдельные записи в кеше.
Если maxsize установлен в
None, механизм LRU отключается, и размер кэша может расти без ограничений. Механизм LRU работает лучше всего, когда maxsize является степенью двойки.Если для 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.
-
@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.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
-
class
functools.partialmethod(func, *args, **keywords)¶ Возвращает новый дескриптор
partialmethod, который ведёт себя какpartial, за исключением того, что он предназначен для использования в качестве определения метода, а не для прямого вызова.func должен быть дескриптором или вызываемым объектом (объекты, являющиеся и тем и другим, как обычные функции, обрабатываются как дескрипторы).
Когда func является дескриптором (например, обычная функция Python,
classmethod(),staticmethod(),abstractmethod()или другой экземплярpartialmethod), вызовы__get__делегируются базовому дескриптору, а в качестве результата возвращается подходящий частичный объект.Когда 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 с двумя аргументами последовательно к элементам 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.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
Если для конкретного типа нет зарегистрированной реализации, используется порядок разрешения методов (MRO) этого типа для поиска более общей реализации. Исходная функция, декорированная
@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()теперь поддерживает использование аннотаций типов.
-
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(): ... """докстринг""" ... print('Called example function') ... >>> example() Calling decorated function Called example function >>> example.__name__ 'example' >>> example.__doc__ 'Docstring'
Без использования этой фабрики декораторов имя примера функции было бы
'wrapper', а строка документации исходнойexample()была бы потеряна.
partial Объекты¶partial Objects
Объекты partial – это вызываемые объекты, создаваемые partial(). Они имеют три атрибута только для чтения:
-
partial.func¶ Вызываемый объект или функция. Вызовы объекта
partialбудут перенаправлены вfuncс новыми аргументами и ключевыми словами.
-
partial.args¶ Крайние левые позиционные аргументы, которые будут добавлены перед позиционными аргументами, переданными при вызове объекта
partial.
Объекты partial похожи на объекты function тем, что они вызываемы, могут быть слабыми ссылками и могут иметь атрибуты.
Есть несколько важных отличий.
Например, атрибуты __name__ и __doc__ не создаются автоматически.
Кроме того, объекты partial, определённые в классах,
ведут себя как статические методы и не превращаются в связанные методы при поиске атрибута экземпляра.