python

Введение в Python Functools

  • четверг, 28 мая 2020 г. в 00:37:01
https://habr.com/ru/company/otus/blog/504102/
  • Блог компании OTUS. Онлайн-образование
  • Python
  • Программирование


Привет, хабровчане. Мы подготовили перевод еще одного полезного материала в преддверии старта курса «Разработчик Python».




Python – высокоуровневый объектно-ориентированный язык программирования. Одно из самых больших преимуществ Python заключается в том, что в нем есть специальный функционал, который позволяет писать переиспользуемый код с помощью встроенных инструментов языка.

Functools – это библиотека Python, которая предназначена для работы с функциями высшего порядка. Такие функции могут принимать в себя другие функции и возвращать функции. Они помогают разработчиком писать код, который можно переиспользовать. Функции можно использовать или расширять, не переписывая их полностью. Модуль functools в Python предоставляет различные инструменты, которые позволяют добиться описанного эффекта. Например, следующие:

  • Partial;
  • Полное упорядочивание;
  • update_wrapper для partial.


Функция partial является одним из основных инструментов, предоставляемых functools. Давайте разберемся с ней на примерах.

Функция partial в Python


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

Мы можем создавать новые функции, передавая частичные аргументы. Также мы можем заморозить некоторые аргументы функции, что приведет к появлению нового объекта. Еще один способ представить partial, заключается в том, что с ее помощью мы можем создать функцию со значениями по умолчанию. Partial поддерживает ключевые слова и позиционные аргументы в качестве фиксированных.
Давайте разберемся на примерах.

Как создать функцию partial?


Чтобы создать partial-функцию, используйте partial() из библиотеки functools. Пишется она следующим образом:

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


Так вы создадите partial функцию, которая вызовет func, передав ей фиксированные ключевые слова и позиционные аргументы. Здесь обычно передаются несколько необходимых аргументов для вызова функции func. Остальные аргументы передаются в *args и **kwargs.

Допустим, функция ниже складывает два числа:

def multiply(x, y):
 
    return x * y


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

def multiply(x, y):
        return x * y
 
def doubleNum(x):
       return multiply(x, 2)
 
def tripleNum(x):
       return multiply(x, 3)


Когда сценария работы функции всего 2-3, конечно, логичнее сделать, как показано выше. Но когда нужно написать еще 100 таких функций, то смысла переписывать один и тот же код столько раз нет. Здесь нам и пригодятся partial функции. Чтобы ими воспользоваться, во-первых, нам нужно импортировать partial из Functools.

from functools import partial
 
def multiply(x, y):
       return x * y
 
doubleNum = partial(multiply, 2)
   tripleNum = partial(multiply, 3)
 
Print(doubleNum(10))
 
Output: 20


Как видно из примера, значения по умолчанию будут заменены переменными слева. Вместо x будет 2, а вместо y будет 10 при вызове doubleNum(10). В этом примере порядок не будет иметь принципиального значения, но в других вариантах использования он может иметь значение. Давайте рассмотрим пример на этот случай, чтобы понять порядок замены переменных.

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


Полное упорядочивание


У нас появилась функция orderFunc(), в которой происходит умножение a на 4, b на 3, c на 2 и добавление d к сумме значений.

Мы создали partial функцию result(), которая вызывает orderFunc() со значениями 5, 6 и 7. Теперь значения 5, 6 и 7 будут заменять переменные a, b и c соответственно. На место переменной d встанет 8, так как она передается при вызове result(). В результате получится (4*5 + 6*3 + 7*2 + 8) = 60.

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

from functools import partial
def orderFunc(a,b,c,d):
       return a*4 + b*3 + c*2 + d
 
 result = partial(orderFunc,c=5,d=6)
print(result(8,4))
 
Output: 60


Здесь мы зафиксировали значение 5 за переменной c и 6 за переменной d. Вместо переменных a и b встанут значения 8 и 4. В результате получится (8*4+4*3+5*2+6) = 60.

Partial функцию можно определить в цикле и использовать для повторяющихся вычислений. Давайте рассмотрим пример:

from functools import partial
 
def add(x,y):
      return x + y
 
add_partials = []
for i in range (1, 10):
      function = partial(add, i)
      add_partials.append(function)
      print('Sum of {} and 2 is {}'.format(i,add_partials[i-1](2)))
 
  
Output:
 
Sum of 1 and 2 is 3
Sum of 2 and 2 is 4
Sum of 3 and 2 is 5
Sum of 4 and 2 is 6
Sum of 5 and 2 is 7
Sum of 6 and 2 is 8
Sum of 7 and 2 is 9
Sum of 8 and 2 is 10
Sum of 9 and 2 is 11


В этом примере мы будем суммировать определенный диапазон значений с 2, переиспользуя имеющуюся функцию. Мы можем вызвать partial в цикле и использовать ее функционал для вычисления сумм. Как видно из значений на выходе, у нас есть цикл от 1 до 10 и все значения в этом промежутке прибавляются к 2 с помощью функции partial, которая вызывает функцию сложения.

Метаданные


Несмотря на то, что partial функции являются независимыми, они хранят память (метаданные) функции, которую они расширяют.

from functools import partial
 
def add(x,y):
      return x + y
 
# create a new function that multiplies by 2
result = partial(add,y=5)
print(result.func)
print(result.keywords)
 
Output:
<function add at 0x7f27b1aab620>
{'y': 5}


Первый вызов func передаст имя функции и ее адрес в памяти, а второй вызов с keywords передаст ключевые слова в функцию. Таким образом функции partial можно назвать самодокументирующимися с помощью метаданных, которые они получают от расширяемой функции.

update_wrapper для partial

Мы можем обновлять метаданные функции с помощью другого инструмента из functools. Update_wrapper – это инструмент, который можно использовать для обновления метаданных функции. Давайте разберемся с ним на примере.

def multiply(x, y):
 
    """Test string."""
 
    return x * y
 
 result = functools.partial(multiply, y=2)
 
 try:
 
    print ('Function Name:'+result.__name__)
 
except AttributeError:
 
    print('Function Name: __no name__')
 
 print ('Function Doc:'+result.__doc__)
 
 print('Updating wrapper:')
 
functools.update_wrapper(result, multiply)
 
 print ('Function Name:'+result.__name__)
 
print ('Function Doc:'+result.__doc__)
 
 
 
Output:
 
Function Name: __no name__
 
Function Doc:partial(func, *args, **keywords) - new function with partial application
 
    of the given arguments and keywords.
 
 
Updating wrapper:
 
Function Name: multiply
Function Doc: Test string.


Теперь как видно из выходных данных, до использования обертки (wrapper) у функции не было закрепленного за ней имени или документа. Как только мы обновили name и doc функции с помощью update_wrapper, в выводе увидели соответствующий результат.

Заключение


С помощью functools мы можем избавиться от избыточного кода и увеличить возможности переиспользования кода в Python. Чем чаще вы будете использовать функцию partial, тем больше вариантов использования будете открывать. Экспериментируйте и получайте от этого удовольствие!



Успеть на курс.