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.
Advanced Features & Recent Enhancements
cached_property
(Python 3.8+)
The cached_property
decorator allows you to convert a class method into a property whose value is computed only once and then cached for future use. This is especially useful for expensive operations, such as database queries or complex calculations, where you don’t want to recompute the result every time. Instead, the result is stored and reused whenever you access the property again.
Improved lru_cache
(Python 3.9+)
The lru_cache
decorator has been enhanced to perform better in multi-threaded environments. It now ensures thread-safety, which makes it more reliable when used in concurrent applications. Another improvement is the option to set maxsize=None
, which turns it into an unlimited cache, allowing you to store all function results without eviction.
cache
(Python 3.9+)
A newer addition to the module is the cache
decorator, which acts as a simplified version of lru_cache
. Unlike lru_cache
. It does not have a limit on stored items, making it a good choice when you want unlimited caching and don’t need the overhead of maintaining a least-recently-used strategy.
Enhanced singledispatchmethod
(Python 3.8+)
The singledispatchmethod
decorator brings the power of single dispatch to class methods. This feature allows you to overload methods in a clean and organized way, dispatching them based on the type of the first argument. It improves code readability and helps you manage multiple method variations within a class.
Better partial
Function (Recent Updates)
The partial
function, which allows you to pre-fill arguments of a function, has been optimized to work more efficiently and integrates better with type annotations. This makes it easier to write reusable and maintainable functions, while also keeping your codebase cleaner and more readable.
Metadata Preservation with @wraps
The @wraps
decorator has been refined to ensure that when you wrap a function with a custom decorator, the original function’s metadata, such as its name, docstring, and annotations, are not lost. This enhancement is important for debugging, documentation, and keeping your code transparent and developer-friendly.
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.
Q6. What is the functools module used for in Python?
The functools module in Python is used to provide higher-order functions that help in functional programming, such as function manipulation and optimization.
Q7. How does lru_cache() improve Python performance?
The lru_cache() in Python improves performance by storing results of expensive function calls and reusing them when the same inputs occur again.
Q8. What’s the difference between cache() and lru_cache()?
The cache() stores unlimited results of function calls, while lru_cache() stores a limited number using a least-recently-used eviction policy.
Q9. When should I use functools.partial() vs writing a wrapper?
Use functools.partial() when you just need to fix some function arguments, but write a wrapper when you need more custom logic or processing.
Q10. How does @functools.wraps preserve function metadata?
@functools.wraps preserves function metadata by copying the original function’s name, docstring, and attributes to the wrapper function.
Q11. Can functools.singledispatch be used with annotations?
Yes, functools.singledispatch can work with type annotations by dispatching functions based on the annotated type of the first argument.
Q12. What is cached_property() and when should I use it?
cached_property() is used to cache the result of a property method after the first call, making it useful for expensive computations that don’t change.