Python
7 Uses of Python Functools That Make Your Code More Professional
Elegance is the only beauty that never fades

One of the many advantages of Python is its abundant built-in modules which save us programmers from reinventing the wheel.
The functools module is a good example. Leveraging it well will make our Python pretty neat, clean, and professional.
This article will introduce 7 must-know uses of this outstanding module of Python. After reading, your position as “the Python guru” will be cemented for sure.
1. functools.cache: Avoid Replicate Computing
Python is not as slow as many people described. In fact, a common factor contributing to slow execution speeds is the repetitive computation rather than the language.
I acknowledge that the following Python code, which measures the time required to compute the 30th Fibonacci number over 50 iterations, is considerably slower than its Rust counterpart:
import time
from functools import cache
def fib(n):
if n <= 1:
return n
return fib(n - 1) + fib(n - 2)
def main(test_times=50):
start = time.time()
for _ in range(test_times):
fib(30)
print(f"Total time spent: {time.time() - start} s")
main()
# Total time spent: 7.455092906951904 sApplying Rust to rewrite the time-consuming fib function is a good idea, but not everyone is fond of using two languages for one project.
Since the root of all evil is the replicate computations. Why not cache the middle values?
There comes the @functools.cache decorator in Python. As clear as its name, it will help us do the caching stuff and speed up the whole execution of the code:
import time
from functools import cache
@cache
def fib(n):
if n <= 1:
return n
return fib(n - 1) + fib(n - 2)
def main(test_times=50):
start = time.time()
for _ in range(test_times):
fib(30)
print(f"Total time spent: {time.time() - start} s")
main()
# Total time spent: 1.5974044799804688e-05 s1.5974044799804688e-05 s! Do you still think Python is slow?
2. functools.cached_property: Make a Python Class More Efficient
For Python classes with costly value computation methods, a practical approach is to cache the results as typical attributes, allowing it to persist for the instance’s entire existence.
The functools.cached_property, which was introduced by Python 3.8, is designed to help us do this easily.
from functools import cached_property
class Circle:
def __init__(self, radius):
self.radius = radius
@cached_property
def area(self):
print("Calculating area...")
return 3.14159265359 * self.radius ** 2
@cached_property
def circumference(self):
print("Calculating circumference...")
return 2 * 3.14159265359 * self.radius
# Create a Circle object
my_circle = Circle(5)
# Access the area property
print(my_circle.area) # This computes and caches the area
print(my_circle.area) # This retrieves the cached area
# Access the circumference property
print(my_circle.circumference) # This computes and caches the circumference
print(my_circle.circumference) # This retrieves the cached circumferenceAs the above example shows, there is no need to repeatedly calculate the area and circumference of my_circle each time as long as it has been calculated once. With the help of the simple Python decorator, @cached_property, our program is much more efficient and professional.
3. functools.total_ordering: Define One Comparison and Let Python Do the Rest
If there is something that can understand existing code and generate the rest of the code for you, you probably think I’m talking about ChatGPT.
Surprisingly, Python can do this as well in some cases.
The functools.total_ordering decorator is what I’m saying. Due to this, we can merely define one or more rich comparison ordering methods for a Python class, and this decorator will supply the rest intelligently. (Of course, it’s not AI, but the results are as intelligent as it is).
from functools import total_ordering
@total_ordering
class Leader:
def __init__(self, first_name, last_name, age):
self.first_name = first_name
self.last_name = last_name
self.age = age
def __lt__(self, other):
return self.age < other.age
leader1 = Leader("Yang", "Zhou", 30)
leader2 = Leader("Elon", "Musk", 52)
print(leader1 < leader2) # True
print(leader1 > leader2) # False
print(leader1 == leader2) # FalseAs demonstrated above, we just wrote one method for defining the “less than” relationship between two instances. However, thanks to the total_ordering decorator, Python is smart enough to know the “equal” and “larger than” comparisons already. This simplifies our code so much.
4. functools.partial: Customize Python Built-In Functions for Convenience
Python has many easy-to-use built-in functions, but sometimes we have to add specific parameters to them for special cases.
For instance, the int() function is to convert a string into an integer. If the string represents a binary integer, we must add the second argument for it:
print(int('10101', base=2))
# 21It’s annoying if we need to use this function frequently but always write the same second parameter repeatedly.
The partial method from the functools is here to make our code clean again:
from functools import partial
basetwo = partial(int, base=2)
print(basetwo('10101'))
# 21
print(basetwo('1111111'))
# 127
print(basetwo('100101101'))
# 301As the above code shows, we can define a “customized” Python built-in function with the pre-defined parameters. So anytime we need to use it again, we can just call the new function instead of the original one.
5. functools.singledispatch: Define a Generic Python Function and its Overloaded Implementations
Python is a dynamic typing language, which is convenient for us to write neater code.
However, sometimes we have to consider the type of a function’s parameter. For example, the following function connect() needs to know how to handle different types of receiving address:
def connect(address):
if isinstance(address, str):
ip, port = address.split(':')
elif isinstance(address, tuple):
ip, port = address
else:
print('Wrong address!')Without a doubt, we can use many if-else conditions to handle different address formats. However, too much if-else code for a simple function is a symbol for ugly.
“Beautiful is better than ugly.” This is the first sentence of “The Zen of Python”. So Python definitely should provide an elegant solution for this scenario.
A beautiful approach came from functools since Python 3.4 — singledispatch.
from functools import singledispatch
@singledispatch
def connect(address):
print('Wrong address format!')
@connect.register
def _(address: str):
ip, port = address.split(':')
print(f'IP:{ip}, Port:{port}')
@connect.register
def _(address: tuple):
ip, port = address
print(f'IP:{ip}, Port:{port}')
connect('yangzhou1993.medium.com:443')
# IP:yangzhou1993.medium.com, Port:443
connect(('yangzhou1993.medium.com', 443))
# IP:yangzhou1993.medium.com, Port:443
connect(2077)
# Wrong address format!As shown above, we can use the @singledispatch decorator to define a generic function. Then using the register() attribute of the generic function as a decorator to its overloaded implementations. Each implementation handles one specific type of parameter. Finally, when we call the connect() function, Python will automatically find the proper implementation for execution.
Isn’t this method more professional than using numerous ‘if-else’ statements?
6. functools.reduce: Handle a Python Iterable Cumulatively
Reduce is one of the significant higher-order functions of Python which provides much convenience for developers to write elegant code.
It applies one function of two arguments cumulatively to the items of an iterable, from left to right, so as to reduce the iterable to a single result.
Talk is cheap, let’s see an intuitive example:
from functools import reduce
chars = ['L', 'o', 'n', 'd', 'o', 'n', 2, 0, 7, 7]
city = reduce(lambda x, y: str(x) + str(y), chars)
print(city)
# London2077As the above program shows, with the help of functools.reduce function, we merely used one line of code to combine the characters into a whole string, no for-loops are needed.
7. functools.wraps: Keep the Metadata of Original Functions
Decorators in Python can help us write more low-coupling code. However, they are not easy to handle. Sometimes we may meet unexpected issues in normal code:
def add_author(func):
def wrapper(*args, **kwargs):
author = 'Yang Zhou'
return author + '\n' + func(*args, **kwargs)
return wrapper
@add_author
def get_title(title):
"""
A func that receives and returns a title.
"""
return title
print(get_title('7 Uses of Python Functools That Make Your Code More Professional'))
# Yang Zhou
# 7 Uses of Python Functools That Make Your Code More Professional
print(get_title.__name__)
# wrapper
print(get_title.__doc__)
# NoneThe above code successfully defined and applied the decorator @add_author to the function get_title.
However, it seems strange that it can’t print the right information of the get_title functions. This would result in serious bugs in production systems because you probably would call a wrong function if you can’t even get a correct name.
This is a side effect of using decorators. You will not only “wrap” the function, but also its metadata, such as name, doc, and so on.
To avoid it, we can write the correct metadata manually for each wrapped function. But there is a much easier way of using functools.wraps:
from functools import wraps
def add_author(func):
@wraps(func)
def wrapper(*args, **kwargs):
author = 'Yang Zhou'
return author + '\n' + func(*args, **kwargs)
return wrapper
@add_author
def get_title(title):
"""
A func that receives and returns a title.
"""
return title
print(get_title('7 Uses of Python Functools That Make Your Code More Professional'))
# Yang Zhou
# 7 Uses of Python Functools That Make Your Code More Professional
print(get_title.__name__)
# get_title
print(get_title.__doc__)
# A func that receives and returns a title.As demonstrated above, functools.wraps is a valuable tool for safeguarding the original function’s metadata. In my view, it is a good practice for Python developers to incorporate the @wraps decorator into every wrapper function to prevent unexpected outcomes.
Conclusion
The functools module in Python can not only streamline our code but also make it more professional and maintainable.
As Python developers, mastering the art of using functools effectively can make a significant difference in our programming journey.
Thanks for reading ❤️, feel free to connect with me:
References:





