functools – это стандартный модуль, предоставляющий набор функций, которые воздействуют на другие функции.
А точнее, они могут быть применены к любому объекту, реализующему метод __call__.
К ним относятся partial(), позволяющая “заморозить” функцию с определенными аргументами, lru_cache(), позволяющая кэшировать результат работы функции, singledispatch(), обеспечивающая возможность реализации общих функций и другие.
partial
Это, пожалуй, одна из самых полезных функций во всей стандартной библиотеке.
partial() получает функцию A с аргументами и возвращает новую функцию B, вызов которой эквивалентен вызову функции A с указанными аргументами.
>>> from functools import partial
>>> def add(a, b):
... return a + b
...
>>> f = partial(add, 7, 5)
>>> f()
12
Code language: JavaScript (javascript)
Хотя этот пример может показаться довольно тривиальным, он часто оказывается чрезвычайно полезным.
Например, предположим, что гипотетическая функция do_work() выполняет определенную работу асинхронно и по ее завершении вызывает указанную нами функцию.
def callback():
print("Работа завершена.")
do_work(callback)
Code language: PHP (php)
Если бы у нас был объект, которому мы хотим передать callback, мы бы просто сделали:
def callback(a):
print("Работа завершена.")
a = 1
do_work(partial(callback, a))
Code language: PHP (php)
Это полностью устраняет риск использования глобального объекта, который виден обоим фрагментам кода.
Другой особенностью partial является возможность указания только некоторых аргументов, поэтому она возвращает частичную функцию по отношению к исходной.
Например, чтобы преобразовать строку в шестнадцатеричном формате в соответствующее целое число, вы можете:
>>> int("ff", base=16)
255
Code language: JavaScript (javascript)
Теперь, если наша программа использует результат этого преобразования, мы можем создать новую функцию from_hex, которая избавит нас от необходимости указывать base=16.
>>> from_hex = partial(int, base=16)
>>> from_hex("ff")
255
Code language: JavaScript (javascript)
Что является упрощением:
>>> def from_hex(a):
... return int(a, base=16)
Code language: PHP (php)
Функции, возвращаемые partial(), содержат имя функции, которую они вызывают, и соответствующие аргументы.
>>> from_hex.func
<class 'int'>
>>> from_hex.args
()
>>> from_hex.keywords
{'base': 16}
Code language: JavaScript (javascript)
Функция functools.partialmethod (появилась в версии 3.4) аналогична, но работает над методами внутри класса:
from functools import partialmethod class Window: def __init__(self): self._visible = True def set_visible(self, visible): self._visible = visible show = partialmethod(set_visible, True) hide = partialmethod(set_visible, False) window = Window() window.hide()
lru_cache
functools.lru_cache() (появился в версии 3.2) – это декоратор для кэширования результата функции.
То есть, если функция вызывается 4 раза с одними и теми же аргументами, последние три вызова просто возвращают кэшированное значение, не выполняя функцию.
В “тяжелых” задачах это значительная оптимизация памяти и скорости.
Наиболее наглядным примером является функция, возвращающая содержимое URL:
from functools import lru_cache
from urllib.request import urlopen
@lru_cache()
def read_url(url):
with urlopen(url) as r:
return r.read()
print(len(read_url("https://dvsemenov.ru")))
print(len(read_url("https://dvsemenov.ru")))
print(len(read_url("https://dvsemenov.ru")))
print(len(read_url("https://dvsemenov.ru")))
Code language: JavaScript (javascript)
При выполнении этого кода в моем случае первый вызов занимает около 1 секунды, а последние три возвращаются мгновенно.
Мы можем указать, через сколько вызовов функции кэш должен закончиться.
# Кэш действует в течение 6 вызовов.
@lru_cache(maxsize=6)
def read_url(url):
# ...
Code language: PHP (php)
Чтобы срок действия кэша никогда не истекал, необходимо указать maxsize=None.
Значение по умолчанию – 128.
При этом кеш функции может быть очищен вручную с помощью функции cache_clear():
>>> read_url.cache_clear()
Code language: CSS (css)
Для получения информации о кэше:
>>> read_url.cache_info() CacheInfo(hits=3, misses=1, maxsize=6, currsize=1)
Обратите внимание, что функции, декорированные с помощью lru_cache(), могут принимать в качестве аргументов только объекты, реализующие метод hash (хэшируемые объекты).
К ним относятся, например, все неизменяемые объекты Python: целое число, строка, кортеж, но не список или словарь.
>>> @lru_cache()
... def f(a):
... print(a)
...
>>> f([1, 2, 3])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
Code language: CSS (css)
singledispatch
singledispatch() (доступна начиная с Python 3.4) – это попытка ввести в язык общие функции (те, которые определяются несколько раз в зависимости от типа данных, которые они получают в качестве аргумента).
Однако Python далек от того, чтобы предоставить полноценное решение для общих функций.
Этот метод является single в том смысле, что он учитывает только тип первого аргумента для выполнения диспетчеризации, т.е. вызова соответствующей функции.
Для примера рассмотрим дублирующую функцию, которая для целых чисел возвращает результат в два раза больше аргумента.
>>> def duplicate(a):
... return a * 2
...
>>> duplicate(5)
10
Code language: PHP (php)
Далее мы хотим, чтобы функция duplicate() применялась и к строкам, чтобы она возвращала заданный текст дважды с пробелом между ними.
>>> from functools import singledispatch
>>> @singledispatch
... def duplicate(a):
... return a * 2
...
>>> @duplicate.register(str)
... def _(s):
... return " ".join((s, s))
...
>>> duplicate(5)
10
>>> duplicate("Python")
'Python Python'
Code language: JavaScript (javascript)
Вот что произошло.
Сначала с помощью декоратора @singledispatch мы определили главную функцию.
Затем, используя @duplicate.register(), мы определяем ту же функцию, но для конкретных типов данных (в данном случае для типа str).
Когда мы вызываем duplicate(), Python проверяет объект, переданный в качестве аргумента, и в соответствии с его типом данных вызывает соответствующую функцию.
Если тип данных не был специально зарегистрирован через @duplicate.register(), то вызывается основной.
Одна и та же функция может быть определена для двух типов данных.
Например, мы можем считать эквивалентными список и кортеж:
@duplicate.register(tuple)
@duplicate.register(list)
def _(list_or_tuple):
# ...
Code language: PHP (php)
register() может также применяться как функция:
>>> def duplicate_str(s):
... return " ".join((s, s))
...
>>> duplicate.register(str, duplicate_str)
Code language: CSS (css)
Чтобы узнать, какая функция будет вызвана перед определенным типом:
>>> duplicate.dispatch(str)
<function duplicate_str at 0x00000005D309ABF8>
Code language: HTML, XML (xml)
Преимущество использования общих функций перед ручной проверкой (через type() или isinstance()) типа данных объекта заключается в возможности регистрации пользователем собственных функций.
Например, используя функцию singledispatch(), функция pprint.pprint могла бы позволить разработчику вводить новые типы данных для отображения на экране.
total_ordering
Применяемый к классу, этот декоратор автоматически определяет три из четырех методов сравнения __lt()__, __le()__, __gt()__ и __ge()__.
Единственным требованием является явное определение одного из них вместе с методом равенства __eq()__.
Например, рассмотрим класс String, который, в отличие от типа str, определяет свои параметры сравнения по длине строки, поэтому “Привет” равно “Здоров”, так как оба содержат 4 символа.
class String: def __init__(self, string): self._string = string def __eq__(self, other): return len(self._string) == len(other._string) >>> String("Привет") == String("Здоров") True
Необходимо только определить еще один метод сравнения, чтобы при применении декоратора остальные генерировались автоматически.
@total_ordering
class String:
def __init__(self, string):
self._string = string
def __eq__(self, other):
return len(self._string) == len(other._string)
def __lt__(self, other):
return len(self._string) < len(other._string)
Code language: CSS (css)
Проверка:
>>> String("Привет") == String("Здоров") # __eq__
True
>>> String("Привет") > String("Здоров") # __gt__
False
>>> String("Привет") >= String("Здоров") # __ge__
True
>>> String("Привет") < String("Здоров") # __lt__
False
>>> String("Привет") <= String("Здоров") # __le__
True
Code language: PHP (php)
Как предупреждает официальная документация, использование @total_ordering приводит к более низкой скорости выполнения, чем его эквивалент при ручном определении всех методов.
reduce
Эта функция “сводит” итерируемый объект (например, список или кортеж) к одному объекту.
Какой объект он возвращает и какие операции он применяет к итерируемому объекту, зависит от переданной ему функции.
>>> from functools import reduce
>>> words = ["Привет", "мир", "это", "Python"]
>>> def add_str(a, b):
... return " ".join((a, b))
...
>>> reduce(add_str, words)
'Привет мир это Python'
Code language: JavaScript (javascript)
В примере мы определяем функцию, которая конкатенирует две строки с пробелом между ними.
Далее мы применяем его к списку слов с помощью reduce().
Она является “кумулятивной” в том смысле, что результат каждой операции (в данном случае add_str()) используется для следующей.
>>> ret = add_str(words[0], words[1])
>>> ret
'Привет мир'
>>> ret = add_str(ret, words[2])
>>> ret
'Привет мир это'
>>> ret = add_str(ret, words[3])
>>> ret
'Привет мир это Python'
Code language: JavaScript (javascript)
cmp_to_key
Основная цель этой функции, появившейся в версии 3.2, – облегчить преобразование кода из Python 2 в Python 3.
В основном она применяется (но не ограничивается) функцией sorted, которая используется для сортировки итерируемых объектов, таких как список или кортеж.
>>> sorted([2, 1, 4, 3])
[1, 2, 3, 4]
Code language: CSS (css)
Хотя поведение вполне определенное для таких элементов, как целые числа, оно может отличаться для других более сложных типов.
Например, по умолчанию строки сортируются по порядковому номеру первого символа.
>>> it = ["Python", "C++", "Java"]
>>> sorted(it)
['C++', 'Java', 'Python']
Code language: JavaScript (javascript)
(Это часто неправильно интерпретируется как алфавитный порядок).
Обычно строки сортируются по длине.
Для этого исторически Python использовал так называемую функцию cmp, для сравнения двух элементов a и b, которая должна возвращать отрицательное число, если a < b, ноль, если a == b, и положительное число, если a > b.
Чтобы отсортировать строки по длине, мы можем использовать:
>>> def cmp_by_len(a, b):
... return len(a) - len(b)
...
>>> it = ["Python", "C++", "Java"]
>>> sorted(it, cmp=cmp_by_len)
['C++', 'Java', 'Python']
Code language: PHP (php)
Это был “стандартный” метод определения того, как должен быть отсортирован данный тип данных.
С появлением Python 3 команда разработчиков внесла изменения в дизайн и заменить функцию cmp на более простую и эффективную концепцию под названием key.
В новой парадигме программист должен предоставить не функцию, которая сравнивает два элемента, а функцию, которая возвращает числовое значение для одного элемента.
Тогда функция сортировки (в данном случае sorted) просто позаботится о размещении элементов в соответствии с их числовым значением.
Таким образом, наша операция сортировки по длине строки становится намного проще:
>>> it = ["Python", "C++", "Java"]
>>> sorted(it, key=len)
['C++', 'Java', 'Python']
Code language: JavaScript (javascript)
Что произошло.
sorted() вызывает len с каждым из элементов.
Для элемента “Python” функция вернет 6, для “C++” – 3, а для “Java” – 4.
Затем она просто сортирует результаты от наименьшего к наибольшему, и последовательность [6, 3, 4] становится [3, 4, 6], то есть [‘C++’, ‘Java’, ‘Python’].
Теперь, как мы видели в примерах, sorted() может сортировать либо по функции cmp, либо по функции key.
Оба варианта сосуществуют в Python 2, но в Python 3 возможность использования cmp была полностью удалена.
functools.cmp_to_key() позволяет автоматически преобразовывать функции из cmp в метод key, так что очень сложные функции сортировки не нужно полностью переписывать.
>>> from functools import cmp_to_key
>>> def cmp_by_len(a, b):
... return len(a) - len(b)
...
>>> it = ["Python", "C++", "Java"]
>>> sorted(it, key=cmp_to_key(cmp_by_len))
['C++', 'Java', 'Python']
Code language: JavaScript (javascript)
update_wrapper
Копирует атрибуты функции A в функцию B.
>>> from functools import update_wrapper
>>> def foo():
... """Описание функции foo()."""
... pass
...
>>> def bar():
... pass
...
>>> update_wrapper(bar, foo)
<function foo at 0x000000890610F048>
>>> bar.__doc__
'Описаниеаниеиееfoo().'
Code language: JavaScript (javascript)