Функции в любом языке программирования - это что-то вроде мини-программ, которые заключены в специальную “оболочку” с уникальным именем. При необходимости ее можно вызвать указав только имя. Это значительно упрощает программирование на любом языке, в том числе и Python. Однако раздел с функциями весьма разнообразен и требует знания определенных правил для эффективной работы. С помощью сторонних инструментов процесс создания и применения функций в коде можно оптимизировать. В качестве такого инструмента как раз и выступает Functools.
О работе с функциями в Python
Функции в языке программирования Python обозначаются ключевым словом def. В скобках после ключевого слова можно добавить какой-нибудь аргумент или аргументы, разделив их запятой. Можно ничего не добавлять, оставив просто незаполненным пространство в круглых скобках. Затем ставится двоеточие и ниже пишется само тело функции, которое будет исполняться при ее вызове.
Пример синтаксиса функции в Python:
def my_func():
print("Hello world!")
Важно сохранять отступы, чтобы Python корректно определил код к ранее созданной функции.
Функция, которая была использована в примере самая простая - она просто выводит сообщение “Hello world!” при вызове. Для ее инициализации нужно просто указать ее имя в нужном месте кода и задать аргументы, если в этом имеется необходимость. Однако функции в Python встречаются гораздо более сложные. Иногда они по объему и количеству выполняемых действий сопоставимы с мини-программами. В таком случае их вызов тоже может усложняться, как минимум, из-за необходимости передачи большого количества аргументов при вызове.
Еще функция может включать в себя другую функцию. В свое время функция, находящаяся внутри основной функции, может иметь свои функции, а те в свою очередь свои и так, в теории, до бесконечности. Работать с такими большими объемами и корректно проводить их вызовы в нужных местах программы бывает сложно. Специально для упрощения некоторых задач с функциями в Python существуют вспомогательные инструменты. Functools является одним из самых известных решений подобного типа.
Библиотека Functools
Functools - это подключаемая библиотека, созданная для упрощения работы в первую очередь с многоуровневыми функциями, включающими в себя другие функции или большое количество принимаемых аргументов. Они способны также принимать и возвращать другие функции, что позволяет переиспользовать код. Особенно это полезно в больших проектах, где часто встречаются похожие по возможностям элементы. Разработчику бывает трудно вручную правильно их все упорядочить и дать верные аргументы.
Подключаемая библиотека Functools с помощью собственных функций и ключевых слов позволяет разработчику правильно организовать многоуровневую структуру в проекте, вложенности, выполнить корректную передачу аргументов и внесения изменений в уже созданные функции, даже если те вложены в другие уже использующиеся функции.
Возможности Functools
В модуль Functools включено несколько функций и объектов, упрощающих жизнь разработчику. Некоторые из используются достаточно редко, поэтому рассматривать их подробно не имеет смысла. Те возможности, что используются в Functools достаточно часто будут рассмотрены более подробно с практическими примерами.
Примечательно, что имеющиеся возможности Functools разработчик может не только настраивать под себя, но и усовершенствовать или полностью менять. Это может пригодиться для работы над каким-нибудь специфическим проектом, однако большинству разработчиков вполне хватает базового набора инструментов в Functools.
Функция partial
По умолчанию ее нет в Python, однако она добавляется при использовании модуля Functools. Функция partial является главным инструментом данного дополнения. С ее помощью, например, можно выполнить быструю замену функции или ее содержимого даже если ей уже были заданы аргументы в коде. Что касается аргументации, то ее можно удалить и заменить, что приводит к появлению нового объекта. Дополнительно partial поддерживает имеет поддержку ключевых слов и фиксированных аргументов.
Так как функция partial уже встроена в библиотеку Functools вызывается она как обычная функция. Однако ей нужно будет передать некоторые ключевые значения в качестве аргументации.
Шаблон для вызова partial выглядит так:
partial(func, /, *args, ** kwargs)
В этом примере создается partial-функция, вызывающая func с переданными ключевыми словами и фиксированными аргументами. В месте, где стоит знак “/” передаются аргументы, необходимые для самой функции, однако необязательные для корректного вызова и работы partial. Их может быть несколько. Другие важные для работы элементы необходимо передать в *args и *kwargs. Как итог:
- / - для основного аргумента функции;
- *args - дополнительные аргументы;
- *kwargs - для ключевых аргументов.
Функция partial выполняет возвращение какого-то объекта из кода, необязательно это должна быть другая функция, однако при вызове этот объект будет преобразован в функцию и будет вести себя соответствующе. В качестве примера применения partial рассмотрим простую функцию, выполняющую умножение двух чисел:
def multiply(x, y):
return x * y
Предположим нам нужно удвоить значение, полученное после выполнения данной функции. В качестве примера это можно реализовать через возвращение результата, но уже другой функции:
def doubleNum(x):
return multiply(x, 2)
Приведенный выше пример - это один из вариантов удвоения возвращаемых чисел. При текущих условиях он является оптимальным решением возникшей задачи. Однако, если у вас не 1-2 дополнительных условия, а 100, то писать для каждого отдельную функцию с однотипным содержанием будет долго. Как раз здесь можно и применить инструмент partial. Допустим, имеется функция следующего вида:
def orderFunc(a,b,c,d):
return a*4 + b*3 + c*2 + d
Вам требуется выполнить замену переменных. Это как раз будет удобнее реализовать через инструмент partial. Подключите его, сделав импорт из Functools:
from functools import partial
Импорт можно прописать и до, после основной функции, однако его нельзя прописать уже после применения инструмента partial. Вот пример применения самого инструмента:
result = partial(orderFunc,5,6,7)
print(result(8))
Output: 60
Полный вид кода с partial и конечный результат
Инструмент partialmethod
Во многом похож на стандартный partial за исключением того, что используется не для прямого вызова функции, а для работы с определением метода. Имеет такой же синтаксис:
partialmethod(func, /, *args, **kwargs)
При использовании данного инструмента нужно учитывать три правила:
- Func обязательно должен быть дескриптором или вызываемым объектом. В последнем случае он все равно будет обработан как дескриптор.
- Если func является дескриптором, то он будет делегирован базовому дескриптору и возвращен в качестве частичного объекта. К дескрипторам в данном случае относятся: обычные функции Python, classmethod(), staticmethod(), abstractmethod() или другие экземпляры partialmethod.
- Если вызов func происходит без дескриптора, то соответствующий связанный метод создаётся динамически. Он будет вести себя как обычная функция Python при использовании в качестве метода: аргумент self будет вставлен в качестве первого позиционного аргумента.
Работа с упорядочиванием
В предыдущем примере была функция, где производилось умножение 4 чисел на другие, заранее обозначенные числа. Также к конечному результату требовалось добавить заранее заданное число, которое было указано в переменной d. Вот такой код получился на выходе:
from functools import partial
def orderFunc(a,b,c,d):
return a*4 + b*3 + c*2 + d
result = partial(orderFunc,5,6,7)
print(result(8))
Output: 60
В результате применения инструмента partial для функции result произошла перестановка чисел на позиции a, b и c соответственно. Аргумент для d, который должен прибавляться к конечному результату, был добавлен на этапе вывода при вызове функции result() для ее отображения на экране. В итоге получился пример: (4*5 + 6*3 + 7*2 + 8) = 60.
Структура примера говорит о том, что порядок расстановки аргументов будет влиять на конечный результат. Инструмент orderFunc позволяет зафиксировать переменные, определив их к тому или иному символу. Это позволит избежать нежелательной подмены, следовательно, разработчик получит более точный конечный результат. Применение:
result = partial(orderFunc,c=5,d=6) # фиксация чисел для переменной c и d
Возможности упорядочивания в связке с инструментом partial будут полезны для создания циклов с повторяющимися вычислениями. Пример использования такого цикла:
for i in range (1, 10):
function = partial(add, i)
add_partials.append(function)
print('Сумма {} и 2 равна {}'.format(i,add_partials[i-1](2))) #фиксируется значение для 2
Приведенный пример - это простой цикл, который выполняет сложение до тех пор, пока не получит результат больше 10. С каждой итерацией цикла одно из слогаемых увеличивается на единицу. Здесь функция partial используется прямо в теле цикла для фиксации значений. По итогу выполнения цикла получается такой результат:
Сумма 1 и 2 равна 3
Сумма 2 и 2 равна 4
Сумма 3 и 2 равна 5
Сумма 4 и 2 равна 6
Сумма 5 и 2 равна 7
Сумма 6 и 2 равна 8
Сумма 7 и 2 равна 9
Сумма 8 и 2 равна 10
Сумма 9 и 2 равна 11 #здесь цикл завершается
Другие инструменты Functool
Возможности Functool не останавливаются на использовании одной только функции partial и некоторых методов, хотя они и являются основными. Дополнительные инструменты тоже способны облегчить жизнь разработчику, особенно, если речь идет о сложных проектах.
lru_cache
Предназначен для кэширования аргументов часто повторяющихся функций. Вам достаточно вызвать ее с нужными аргументами только один раз и прописать соответствующее дополнение. После этого аргументы будут проверяться только у первой функции, а остальным просто вернется кэшированное значение. Это очень удобно в тех случае, когда приходится работать с большой по объему программой, так как позволяет оптимизировать ее обработку.
Дополнительно у lru_cache можно задавать максимальное количество кэша. За это отвечает аргумент maxsize. По умолчанию он равен 128. Если нужно, чтобы кэширование проводилось без ограничений, то у maxsize нужно поставить значение None.
Пример использования кэширования:
@lru_cache(maxsize=6)
Аргументы последующей функции теперь будут кэшироваться только один раз.
total_ordering
Отвечает за автоматическое определение методов сравнения. Умеет корректно опознавать __lt()__, __le()__, __gt()__ и __ge()__. Обязательным условием для использования является использование вместе с предложенными методами еще и метод сравнения __eq()__.
reduce
Отвечает за сведение итерируемого объекта, например, список к одному объекту. Объект возвращения, а также применяемые к нему операции зависят от переданной функции. Пример использования с конкатенацией строк:
from functools import reduce
words = ["Привет", "мир", "это", "Python"]
def add_str(a, b):
return " ".join((a, b))
cmp_to_key
Основная задача этой функции помочь в преобразовании кода Python разных версий. По понятной причине используется в редких случаях, так как преобразование требуется только для Python 2 в Python 3. Первый уже давно не используется в широкой среде разработки. В основном работает со встроенной функцией sorted. Дело в том, что в Python 2 для определения длины объекта, например, в символах, использовалось ключевое слово cmp. В Python 3 для этой цели теперь применяется key. Инструмент cmp_to_key позволяет выполнить “безболезненно” как раз преобразования такого вида.
Заключение
Инструментарий Functools позволяет упростить написание и использование функций в Python, выполнить оптимизацию программного кода, уменьшить время на написание однотипных участков. В большинстве случаев среди всего инструментария активно используется только функция partial и некоторые дополнения к ней, поэтому начинающему разработчику будет проще освоить инструментарий Functools. Остальные его возможности можно осваивать по мере необходимости - они тоже несложны для изучения и применения.