Python functools

Filed Under: Python

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:
python functools partial example
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:
python functools partial multiple

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:
python functools partial documented
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:
python functools update wrapper
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:

  1. Definition of at least one comparison function is a must like le, lt, gt or ge.
  2. 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:
python functools total ordering
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

Leave a Reply

Your email address will not be published. Required fields are marked *

close
Generic selectors
Exact matches only
Search in title
Search in content
Search in posts
Search in pages