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

Полный вид кода с partial и конечный результат

Инструмент partialmethod

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

partialmethod(func, /, *args, **kwargs)

При использовании данного инструмента нужно учитывать три правила:

  1. Func обязательно должен быть дескриптором или вызываемым объектом. В последнем случае он все равно будет обработан как дескриптор.
  2. Если func является дескриптором, то он будет делегирован базовому дескриптору и возвращен в качестве частичного объекта. К дескрипторам в данном случае относятся: обычные функции Python, classmethod(), staticmethod(), abstractmethod() или другие экземпляры partialmethod.
  3. Если вызов 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. Остальные его возможности можно осваивать по мере необходимости - они тоже несложны для изучения и применения.