Язык программирования Python

functools – операции с функциями в Python

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)

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *