In one of my previous blogs, I have introduced one decorator in Python functools which can cache the intermediate results for recursive functions and web requests automatically. All we need to do is just adding a decorator to the function definition.
In this article, I’ll introduce three more useful features in the functools module. All of them can save us time from typing lots of code, and more importantly, make our code neat, tidy and more readable. Again, these are all built-in to Python 3.7 or above, so we don’t need to download anything.
Please be advised that all the code examples in this article are based on the functools module are imported as follows.
from functools import *
Importing everything might not be necessary in your case. You may want to import the specific object(s) only.
1. Total Ordering

The first one I would like to introduce is "Total Ordering". It is a decorator to be used for a class definition.
When we define a class and we want the instances from the class can be compared between each other such as greater than, equal and less than. Then, we will have to implement the corresponding methods such as __gt__()
, __lt__()
and __eq__()
. If we want this class can handle all the comparing cases, we will have to implement all of these methods:
- equal:
__eq__()
- greater than:
__gt__()
- less than:
__lt__()
- greater than or equal to:
__ge__()
- less than or equal to:
__le__()
Let’s have a look at the example below. Suppose we are defining a class for Employees. We would like to compare employees based on their age.
class Employee:
def __init__(self, name, age):
self.name = name
self.age = age
def __lt__(self, other):
return self.age < other.age
def __eq__(self, other):
return self.age == other.age
Please be noticed that I only defined the methods for "less than" and "equal".
Now, let’s have to instances from this Employee class.
e1 = Employee('Alice', 30)
e2 = Employee('Bob', 28)
Now, if we compare them using less than or equal, there won’t be any problems.
print(e1 < e2)
print(e1 == e2)

Since we never implemented __le__()
for "less than or equal to", the comparison below will fail.
print(e1 <= e2)

The function tool "total ordering" can help in such scenarios. All we need to do is to add a decorator to the class as follows.
@total_ordering
class Employee:
def __init__(self, name, age):
self.name = name
self.age = age
def __lt__(self, other):
return self.age < other.age
def __eq__(self, other):
return self.age == other.age
Now, we can compare two instances from the Employee class in whatever way we want.
print(e1 < e2)
print(e1 > e2)
print(e1 == e2)
print(e1 <= e2)
print(e1 >= e2)

With total ordering, we only need to implement the equal __eq__()
and either one from __gt__()
, __lt__()
, __ge__()
or __le__()
. The total ordering function tool will help us to derive all the other comparison results.
Please also be noticed that this decorator might not be used in a performance-sensitive application because it does evaluate multiple comparison functions in order to "derive" the final results. For example, we have defined "less than" and "equal". Behind the scene, total ordering will evaluate both of them if we are comparing two objects with "less than or equal" conditions.
2. Partial Function

Have you ever defined several functions with very similar signatures and doing a very similar thing? Sometimes, we may need to consider using the partial function in Python.
Let me make up an example. Suppose we want to find all the words that start with "A" from a sentence. We may right a function like this.
def find_A_words(txt):
return re.findall(r'Aw+', txt)
Please be advised that defining such a function might not be necessary but I just want to have a simple one because the regex is not the focus.
Now, we want to define another function to detect words that start with "B". So, we can define another one like this.
def find_B_words(txt):
return re.findall(r'Bw+', txt)
We can test them and they should work.
print(find_A_words('Apple Ate Big Bad Air Bag'))
print(find_B_words('Apple Ate Big Bad Air Bag'))

With the partial function in Python functools module, we define the find_A_words() function and the find_A_words() function as "partial" functions of the re.findall()
function.
find_A_words = partial(re.findall, r'Aw+')
find_B_words = partial(re.findall, r'Bw+')
As shown, the first argument should be the original function. Then, we can give partial argument(s) to the original function. Therefore, when we use the re-defined partial functions, we just need to give the rest of the arguments.
Specifically, for this example, the re.findall()
function takes at least two arguments pattern
and string
. We have already passed the pattern
using the partial function. Therefore, we only need to give the string
argument when we use it.
print(find_A_words('Apple Ate Big Bad Air Bag'))
print(find_B_words('Apple Ate Big Bad Air Bag'))

The results are identical to the previous implementation.
3. Single Dispatch

Sometimes, we need to define a function that takes a parameter in which the data type is not deterministic. For example, it could be a string, integer or something else. Usually, we will have to write several if-else cases to handle this.
Single Dispatch in the Python functools module provides another way to solve this issue. We can define this logic for different data types in multiple functions. Sometimes, it will improve the readability of our code.
Suppose we are sending a greeting message to the user. It can be as easy as follows.
def greeting(name):
print(f'Hi, {name}!')
If we pass the name as the argument, no problem.
greeting('Chris')

Now, what if we have a list of users and we want to greet them all? With the single dispatch function tool, we can first define the function using the decorator.
@singledispatch
def greeting(name):
print(f'Hi, {name}!')
Then, we can register more overloaded versions to this function by using the register of the single dispatch.
@greeting.register
def _(name: list):
name_string = ', '.join(name)
print(f'Hi, {name_string}!')
Please be noticed that the function name will be not important at all, so I used the underscore for it.
Then, the single dispatch function tool will detect the signature and decide which function body should be used.
greeting(['Alice', 'Bob', 'Chris'])

Summary

In this article, I have introduced three function tools in the Python functools module. These small tricks may either reduce our efforts of coding or improve its readability. Use them when necessary!
If you feel my articles are helpful, please consider joining Medium Membership to support me and thousands of other writers! (Click the link above)