The functools
module in Python provides higher-order functions that operate on or return other functions. These utilities include tools for creating, using, and modifying functions and callable objects.
Table of Contents
- Introduction
- Key Functions and Classes
cache
cached_property
cmp_to_key
lru_cache
partial
partialmethod
reduce
singledispatch
singledispatchmethod
update_wrapper
wraps
- Examples
- Using
partial
for Function Customization - Implementing Caching with
lru_cache
- Single Dispatch Generic Functions
- Using
- Real-World Use Case
- Conclusion
- References
Introduction
The functools
module provides a collection of functions for higher-order operations. These functions help in creating and working with functions and callable objects, allowing for more modular and reusable code.
Key Functions and Classes
cache
A decorator to cache a function’s return value each time it is called with the same arguments.
from functools import cache
@cache
def fibonacci(n):
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
print(fibonacci(10)) # 55
Output:
55
cached_property
Transforms a method of a class into a property whose value is computed once and then cached as a normal attribute for the life of the instance.
from functools import cached_property
class Circle:
def __init__(self, radius):
self.radius = radius
@cached_property
def area(self):
print("Calculating area")
return 3.14159 * self.radius ** 2
c = Circle(5)
print(c.area) # Calculating area 78.53975
print(c.area) # 78.53975 (cached result)
Output:
Calculating area
78.53975
78.53975
cmp_to_key
Transforms an old-style comparison function to a key function.
from functools import cmp_to_key
def compare(a, b):
return (a > b) - (a < b)
numbers = [5, 2, 9, 1, 5, 6]
sorted_numbers = sorted(numbers, key=cmp_to_key(compare))
print(sorted_numbers) # [1, 2, 5, 5, 6, 9]
Output:
[1, 2, 5, 5, 6, 9]
lru_cache
A decorator to wrap a function with a Least Recently Used (LRU) cache.
from functools import lru_cache
@lru_cache(maxsize=32)
def fibonacci(n):
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
print(fibonacci(10)) # 55
Output:
55
partial
Creates a new partial function which calls the original function with some arguments fixed.
from functools import partial
def multiply(x, y):
return x * y
double = partial(multiply, 2)
print(double(5)) # 10
Output:
10
partialmethod
Creates a new partial method which calls the original method with some arguments fixed.
from functools import partialmethod
class MyClass:
def greet(self, name, greeting="Hello"):
return f"{greeting}, {name}!"
greet_hello = partialmethod(greet, greeting="Hello")
greet_hi = partialmethod(greet, greeting="Hi")
obj = MyClass()
print(obj.greet_hello("Alice")) # Hello, Alice!
print(obj.greet_hi("Bob")) # Hi, Bob!
Output:
Hello, Alice!
Hi, Bob!
reduce
Applies a function of two arguments cumulatively to the items of a sequence, reducing the sequence to a single value.
from functools import reduce
numbers = [1, 2, 3, 4, 5]
result = reduce(lambda x, y: x + y, numbers)
print(result) # 15
Output:
15
singledispatch
Transforms a function into a single-dispatch generic function.
from functools import singledispatch
@singledispatch
def process(value):
print(f"Default process: {value}")
@process.register(int)
def _(value):
print(f"Processing integer: {value}")
@process.register(str)
def _(value):
print(f"Processing string: {value}")
process(10) # Processing integer: 10
process("hello") # Processing string: hello
process([1, 2, 3]) # Default process: [1, 2, 3]
Output:
Processing integer: 10
Processing string: hello
Default process: [1, 2, 3]
singledispatchmethod
Transforms a method into a single-dispatch generic method.
from functools import singledispatchmethod
class Processor:
@singledispatchmethod
def process(self, value):
print(f"Default process: {value}")
@process.register
def _(self, value: int):
print(f"Processing integer: {value}")
@process.register
def _(self, value: str):
print(f"Processing string: {value}")
proc = Processor()
proc.process(10) # Processing integer: 10
proc.process("hello") # Processing string: hello
proc.process([1, 2, 3]) # Default process: [1, 2, 3]
Output:
Processing integer: 10
Processing string: hello
Default process: [1, 2, 3]
update_wrapper
Updates a wrapper function to look more like the wrapped function by copying attributes.
from functools import update_wrapper
def my_decorator(f):
def wrapper(*args, **kwargs):
print("Something is happening before the function is called.")
return f(*args, **kwargs)
update_wrapper(wrapper, f)
return wrapper
@my_decorator
def say_hello():
print("Hello!")
say_hello()
Output:
Something is happening before the function is called.
Hello!
wraps
A decorator for updating a wrapper function to look more like the wrapped function.
from functools import wraps
def my_decorator(f):
@wraps(f)
def wrapper(*args, **kwargs):
print("Something is happening before the function is called.")
return f(*args, **kwargs)
return wrapper
@my_decorator
def say_hello():
print("Hello!")
say_hello()
Output:
Something is happening before the function is called.
Hello!
Examples
Using partial for Function Customization
from functools import partial
def power(base, exponent):
return base ** exponent
square = partial(power, exponent=2)
cube = partial(power, exponent=3)
print(square(4)) # 16
print(cube(2)) # 8
Output:
16
8
Implementing Caching with lru_cache
from functools import lru_cache
@lru_cache(maxsize=None)
def factorial(n):
if n == 0:
return 1
return n * factorial(n - 1)
print(factorial(5)) # 120
print(factorial.cache_info()) # CacheInfo(hits=0, misses=6, maxsize=None, currsize=6)
Output:
120
CacheInfo(hits=0, misses=6, maxsize=None, currsize=6)
Single Dispatch Generic Functions
from functools import singledispatch
@singledispatch
def fun(value):
print(f"Default handler: {value}")
@fun.register(int)
def _(value):
print(f"Handling integer: {value}")
@fun.register(str)
def _(value):
print(f"Handling string: {value}")
fun(10) # Handling integer: 10
fun("hello") # Handling string: hello
fun([1, 2, 3]) # Default handler: [1, 2, 3]
Output:
Handling integer: 10
Handling string: hello
Default handler: [1, 2, 3]
Real-World Use Case
Memoization
Memoization is a common use case for lru_cache
. It can be used to optimize recursive algorithms by storing the results of expensive function calls and reusing them when the same inputs occur again.
from functools import lru_cache
@lru_cache(maxsize=128)
def fibonacci(n):
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
print(fibonacci(50)) # 12586269025
Output:
12586269025
Conclusion
The functools module is for higher-order functions: functions that act on or return other functions. In general, any callable object can be treated as a function for the purposes of this module.