The functools module in Python provides higher-order functions and operations on callable objects. It offers tools for working with functions as first-class objects, enabling function transformation, adaptation, and extension without directly modifying their code. It is one of those modules that may seem simple at first glance, but unlocks powerful tricks to work with your functions more efficiently. Whether you are building a web application, writing data processing scripts, or automating tasks, functools can help you optimize performance and write cleaner, more understandable code. In this blog, you will learn what the functools module offers.
Table of Contents:
Higher-order functions in Python can act on or return other functions. To perform such high-order functions in Python, you can utilize the functools module. This module is a standard library in Python that provides you with tools that make it easier to work with/ change/ reuse functions in ways that usually would not be possible with simple functions alone.
You should use functools to optimize your code, reuse functions with fixed arguments, cache expensive computations, or modify functions without changing their original structure.
It makes your programs more efficient, reduces code repetition, and helps you work smarter, not harder.
Before using any functions from the functools module, you need to import it first. Python makes it simple and efficient by bringing the functools module into consideration.
Syntax:
import functools
Master Python and Elevate Your Tech Skills
Learn from industry pros and build real-world projects
Your functions can take a long time to compute because they perform complex work. In this case, lru_cache helps in remembering the results for you to save time. Thus, if you call the same function again with the same inputs, you don’t need to wait again.
Syntax:
@functools.lru_cache(maxsize=None)
def function_name(parameters):
# function body
Example:
Output:
Explanation: Here, the function performs a computation to multiply numerals (8*8) and caches the result in the first call. In the second call with the same input, it does not recompute it, but instead fetches the result from the cache.
There are times when you always call a function with the same value for one or two arguments. You can use partial() to fix those arguments ahead of time and create a shortcut without the need to repeat yourself.
Syntax:
new_function = functools.partial(original_function, fixed_arguments)
Example:
Output:
Explanation: Here, we fixed the first argument x=3 using partial(). Now find(7) is like calling multiply (3,7). It simplifies the code whenever you want to create custom versions of existing functions.
If you have a list of numbers and you want to apply a function like addition or multiplication to combine them into a single value, reduce() will help you cover that. It does this by taking two items at a time and combining them, repeatedly.
Syntax:
functools.reduce(function, iterable)
Example:
Output:
Explanation: Here, the reduce() function starts by adding 6 + 7 = 13, then 13 + 8 = 21, and finally 21 + 9 = 30.
Get 100% Hike!
Master Most in Demand Skills Now!
It is important to preserve the original function’s name and documentation when you decide to create decorators. Decorators are functions that modify other functions. Without using wraps, important information like the name and docstring gets lost.
Syntax:
@functools.wraps(original_function)
def wrapper_function(arguments):
# function body
Example:
Output:
Explanation: Here, the original function name greet and its docstring “Says hello.” are preserved even after decoration, and this is the direct result of implementing @functools.wraps(func). Also, a decorator wraps another function, and that @wraps helps retain the identity of the wrapped function.
Method |
Description |
Syntax |
Common Use |
lru_cache() |
Caches the results of function calls to avoid recomputation, speeding up your code. |
@functools.lru_cache(maxsize=None) |
Speeding up repeated function calls by caching results. |
partial() |
Creates a new function with some arguments fixed ahead of time. |
functools.partial(func, fixed_args) |
Simplifying complex function calls with preset arguments. |
reduce() |
Reduces an iterable to a single accumulated value by applying a function cumulatively. |
functools.reduce(function, iterable) |
Folding a list into a single result, like sum or product. |
wraps() |
Preserves the original function’s metadata when using decorators. |
@functools.wraps(func) |
Maintaining the original function’s name, docstring, and signature after decoration. |
When you want a case where a single function is made to behave differently depending on the type of argument it receives, you usually have to write a lot of if-else statements. You can attempt to overload a function cleanly, without cluttering your code, using functools.singledispatch.
Syntax:
@functools.singledispatch
def function_name(arg):
# default behaviour
@function_name.register(type)
def function_name_for_type(arg):
# behaviour for the specific type
Example:
Output:
Explanation: Here, you created a function called greet. This function behaves differently based on whether you pass a string or an integer, etc. This helps in making your program smart enough to react according to the input type automatically.
Use case 1: Simplify rich comparisons in classes using functools.total_ordering
When you make custom classes, you often need to define many comparison methods like <, <=, >, >=. Writing all of them manually would be hard. functools.total_ordering solves this by allowing you to define just one or two methods, and it will create the rest for you. This saves you a lot of time when building classes that need full comparison features.
Syntax:
@functools.total_ordering
class ClassName:
def __eq__( self , other):
# Define equality logic
def __lt__( self , other):
# Define less-than logic
Example:
Output:
Explanation: Here, the @functools.total_ordering decorator automatically creates the missing comparison methods (le, gt, and ge) based on the ones you define (eq and lt), so you can use all comparison operators without having to write them yourself.
Use case 2: Simple way to cache without limits
Syntax:
@functools.cache
def function_name(parameters):
# function body
Example:
Output:
Explanation: Here, the first call computes 12*11, but the second call doesn’t; It retrieves the answer from the cache. functools.cache makes it even easier than lru_cache, if you don’t care about memory limits and just want maximum simplicity.
Follow these simple tips to make your work with functools smooth and efficient:
- Always use @wraps when writing decorators to preserve the original function’s name and documentation.
- Use lru_cache() only when your function depends purely on its inputs and has no external side effects.
- Always set a reasonable maxsize when caching to avoid memory overflows.
- Choose partial to make APIs simpler and avoid writing repetitive wrapper functions manually.
- Use reduce only when necessary; Sometimes, a loop can be more readable for beginners.
Free Python Course for Beginners
Code at your own pace and build real projects
Conclusion
A module that makes your code smarter, faster in execution, and easier to work with is one of the qualities that make functools one of the most useful modules in Python programming. Does not matter if you are using caching to optimize performance, using partial to simplify call functions, using reduce to fold sequences, using wraps to preserve metadata; The functools module has it covered with a wide range of useful tools. Using this module will save your time, improve your overall code structure, and unlock potential you might not even be aware of.
To take your skills to the next level, enrol in our Python training course and gain hands-on experience. Also, prepare for job interviews using our Python interview questions, designed by industry experts.
Q1. Is the functools a built-in module in Python?
Yes, it is part of Python’s standard library, and you don’t need to install it externally.
Q2. When should I use lru_cache?
Use it when you have a function whose output depends only on its input, and it is called many times with the same arguments.
Q3. Can I limit how many results lru_cache stores?
Yes, you can set a maxsize parameter like @lru_cache(maxsize=100).
Q4. Why use functools.partial()?
Use it when you want to create a version of a function with fewer parameters for easier use.
Q5. What happens if I don't use wraps in a decorator?
If you don’t use wraps, the decorated function loses its original name, docstring, and metadata which makes it difficult to debug.