Introduction to Python Functools

Hi, Habrovsk. We have prepared a translation of another piece of useful material ahead of the start of the Python Developer course .




Python is a high-level object-oriented programming language. One of the biggest advantages of Python is that it has a special functionality that allows you to write reusable code using the built-in language tools.

Functools is a Python library that is designed to work with higher-order functions. Such functions can take other functions and return functions. They help the developer write code that can be reused. Functions can be used or extended without rewriting them completely. The functools module in Python provides various tools that allow you to achieve the described effect. For example, the following:

  • Partial
  • Full ordering;
  • update_wrapperfor partial .

The partial function is one of the main tools provided by functools . Let's figure it out with examples.

Feature partial in Python


In the functools module , the partial function is considered one of the most important tools. Using a partial function, you can replace an existing function that has already been passed arguments. Moreover, we can also create a new version of the function by adding quality documentation.

We can create new functions by passing partial arguments. We can also freeze some function arguments, which will lead to the appearance of a new object. Another way to represent partial , is that with its help we can create a function with default values. Partial supports keywords and positional arguments as fixed.
Let's look at examples.

How to create a partial function ?


To create a partial function, use functoolspartial() from the library . It is written as follows:

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

So you create a partial function that calls func , passing it fixed keywords and positional arguments. Here, a few necessary arguments are usually passed to call the func function . The remaining arguments are passed in * args and ** kwargs .

Suppose the function below adds two numbers:

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

Now consider the case when we needed to double or triple a given number. In this case, we define new functions as shown below:

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

When the script of the function is only 2-3, of course, it is more logical to do as shown above. But when you need to write another 100 such functions, then it makes no sense to rewrite the same code so many times. This is where partial functions come in handy . To use them, firstly, we need to import partial from 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

As you can see from the example, the default values ​​will be replaced by the variables on the left. Instead of x there will be 2, and instead of y there will be 10 when calling doubleNum (10) . In this example, order will not matter, but in other uses, it may matter. Let's look at an example for this case to understand the order of variable substitution.

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

Full ordering


We have a function orderFunc()in which we multiply aby 4, bby 3, cby 2 and add dvalues ​​to the sum.

We have created a partial function result()that calls orderFunc()with the values ​​5, 6 and 7. Now the values ​​5, 6 and 7 will replace the variables a, band caccordingly. The variable dwill be replaced by 8, since it is transmitted when called result(). The result is (4 * 5 + 6 * 3 + 7 * 2 + 8) = 60.

In this case, the order of the transmitted values ​​will matter, because if the order changes, the result will also change. To fix variables, you can use keywords instead of positional arguments. Let's rewrite the code above using keywords as arguments.

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

Here we fixed the value 5 for the variable cand 6 for the variable d. Instead of variables a, the bvalues ​​8 and 4 will appear. As a result, we get (8 * 4 + 4 * 3 + 5 * 2 + 6) = 60. The

Partial function can be determined in a loop and used for repeated calculations. Let's look at an example:

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

In this example, we will summarize a certain range of values ​​with 2, reusing the existing function. We can call partial in the loop and use its functionality to calculate the sums. As you can see from the values ​​at the output, we have a cycle from 1 to 10 and all values ​​in this interval are added to 2 using the partial function , which calls the addition function.

Metadata


Although partial functions are independent, they retain the memory (metadata) of a function that they extend.

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}

The first call to func will pass the name of the function and its address in memory, and the second call with keywords will pass the keywords to the function. Thus, partial functions can be called self-documenting using metadata that they receive from an extensible function. We can update function metadata using another tool from functools . Is a tool that you can use to update feature metadata. Let's figure it out with an example.

update_wrapper partial

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.

Now, as you can see from the output, before using the wrapper, the function did not have a name or document assigned to it. As soon as we updated the name and doc functions with update_wrapper, we saw the corresponding result in the output.

Conclusion


With functools we can get rid of redundant code and increase the possibilities of reusing code in Python. The more often you use the partial function , the more use cases you will open. Experiment and enjoy it!



Get on the course.



All Articles