One of the biggest power which Python demonstrates is providing tools for writing reusable code. In this lesson, we will learn about Python functools module, which makes writing reusable code easy and very much maintainable.
Python functools module
Python functools
module provides us various tools which allows and encourages us to write reusable code. Some of them are:
- Partial functions
- Updating Partial wrappers
- Total ordering
Let’s start our post with a short and informative discussion on partial functions.
What are partial functions?
Python functools partial functions are used to:
- Replicate existing functions with some arguments already passed in.
- Creating new version of the function in a well-documented manner.
partial functions using functools
The points we stated above can be well understood with some examples. Let’s study them now.
Suppose you have a function called multiplier
which just multiplies two numbers. Its definition looks like:
def multiplier(x, y):
return x * y
Now, what if we want to make some dedicated functions to double or triple a number? We will have to define new functions as:
def multiplier(x, y):
return x * y
def doubleIt(x):
return multiplier(x, 2)
def tripleIt(x):
return multiplier(x, 3)
Well, these were easy but what happens when we need 1000 such functions? Here, we can use partial functions:
from functools import partial
def multiplier(x, y):
return x * y
double = partial(multiplier, y=2)
triple = partial(multiplier, y=3)
print('Double of 2 is {}'.format(double(5)))
Well, that was much shorter, isn’t it? Output of the example remains unaffected as:
We can even make multiple partials in a loop:
from functools import partial
def multiplier(x, y):
return x * y
multiplier_partials = []
for i in range (1, 11):
function = partial(multiplier, i)
multiplier_partials.append(function)
print('Product of 1 and 2 is {}'.format(multiplier_partials[0](2)))
print('Product of 3 and 2 is {}'.format(multiplier_partials[2](2)))
print('Product of 9 and 2 is {}'.format(multiplier_partials[8](2)))
This time, we collected more functions in a list and called them. Output will be:
partial functions are self-documented
Even though partial functions can be treated as completely independent functions, they themselves never lose the memory of the function which powers them.
This can be proved from the doc meta-data they hold:
from functools import partial
def multiplier(x, y):
return x * y
double = partial(multiplier, y=2)
triple = partial(multiplier, y=3)
print('Function powering double is {}'.format(double.func))
print('Default keywords for double is {}'.format(double.keywords))
Output will be:
First call gives the function name with its memory address.
Testing partial functions in functools
It is simple to test a partial function. We can even test its documentation. Let’s see how it is done:
from functools import partial
def multiplier(x, y):
return x * y
double = partial(multiplier, y=2)
triple = partial(multiplier, y=3)
assert double.func == multiplier
assert double.keywords == {'y': 2}
When you run this script, you won’t see any output as Assertions only give an error output when they fail. If they pass, they silently continue the execution of the code.
Update partial function metadata with functool.update_wrapper()
With functools module, we can update metadata of a function with wrappers. Let us look at example code snippet to clarify how this is done:
import functools
def multiplier(x, y):
"""Multiplier doc string."""
return x * y
def show_details(name, function):
"""Details callable object."""
print('Name: {}'.format(name))
print('tObject: {}'.format(function))
try:
print('t__name__: {}'.format(function.__name__))
except AttributeError:
print('t__name__: {}'.format('__no name__'))
print('t__doc__ {}'.format(repr(function.__doc__)))
return
double = functools.partial(multiplier, y=2)
show_details('raw wrapper', double)
print('Updating wrapper:')
print('tassign: {}'.format(functools.WRAPPER_ASSIGNMENTS))
print('tupdate: {}'.format(functools.WRAPPER_UPDATES))
functools.update_wrapper(double, multiplier)
show_details('updated wrapper', double)
Output of this script will be:
Before update wrapper, the partial function didn’t have any data about its name and proper doc string but update_wrapper()
function changed that.
Total ordering with functool
functools module also provide a way to provide automatic comparison functions. There are 2 conditions which needs to be met to accomplish the results:
- Definition of at least one comparison function is a must like
le
,lt
,gt
orge
. - Definition of
eq
function is mandatory.
So, this is what we will do:
from functools import total_ordering
@total_ordering
class Number:
def __init__(self, value):
self.value = value
def __lt__(self, other):
return self.value < other.value
def __eq__(self, other):
return self.value == other.value
print(Number(1) < Number(2))
print(Number(10) > Number(21))
print(Number(10) <= Number(2))
print(Number(10) >= Number(20))
print(Number(2) <= Number(2))
print(Number(2) >= Number(2))
print(Number(2) == Number(2))
print(Number(2) == Number(3))
Output of this script will be:
This was actually easy to understand as it allowed us to remove redundant code in our class definition.
In this lesson, we learned about various ways through which we can improve code reusability with functools
module in Python.
Reference: API Doc