functools
The functools module provides tools for working with functions and other callable objects. Its most commonly used features are lru_cache for memoization, partial for partial function application, and wraps for writing well-behaved decorators.
Key Functions
lru_cache
Caches the results of a function based on its arguments. Subsequent calls with the same arguments return the cached result instead of recomputing.
from functools import lru_cache
@lru_cache(maxsize=128)
def fibonacci(n: int) -> int:
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
print(fibonacci(50))
# 12586269025
print(fibonacci.cache_info())
# CacheInfo(hits=48, misses=51, maxsize=128, currsize=51)
Use maxsize=None for an unbounded cache, or @cache (Python 3.9+) as a shorthand for @lru_cache(maxsize=None).
partial
Creates a new function with some arguments pre-filled.
from functools import partial
def power(base, exponent):
return base ** exponent
square = partial(power, exponent=2)
cube = partial(power, exponent=3)
print(square(5)) # 25
print(cube(3)) # 27
wraps
Copies metadata (name, docstring, module) from a wrapped function to its wrapper. Essential for writing decorators that don’t obscure the original function’s identity.
from functools import wraps
def log_calls(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}")
return func(*args, **kwargs)
return wrapper
@log_calls
def greet(name: str) -> str:
"""Return a greeting."""
return f"Hello, {name}"
print(greet("Alice"))
# Calling greet
# Hello, Alice
print(greet.__name__) # "greet" (not "wrapper")
print(greet.__doc__) # "Return a greeting."
Without @wraps, greet.__name__ would be "wrapper" and the docstring would be lost.
reduce
Applies a two-argument function cumulatively to the items of an iterable, reducing it to a single value.
from functools import reduce
numbers = [1, 2, 3, 4, 5]
total = reduce(lambda a, b: a + b, numbers)
print(total) # 15
product = reduce(lambda a, b: a * b, numbers)
print(product) # 120
# With an initial value
total_with_start = reduce(lambda a, b: a + b, numbers, 100)
print(total_with_start) # 115
cached_property
A decorator that converts a method into a property that is computed once and then cached as a normal attribute. Available since Python 3.8.
from functools import cached_property
class Dataset:
def __init__(self, values: list[float]):
self.values = values
@cached_property
def mean(self) -> float:
print("Computing mean...")
return sum(self.values) / len(self.values)
data = Dataset([1.0, 2.0, 3.0, 4.0, 5.0])
print(data.mean) # Computing mean... \n 3.0
print(data.mean) # 3.0 (cached, no recomputation)
Common Patterns
Memoizing expensive computations
from functools import lru_cache
@lru_cache(maxsize=256)
def expensive_query(user_id: int, date: str) -> dict:
# Simulate a slow database query
return {"user_id": user_id, "date": date, "data": "..."}
Creating callback variants with partial
from functools import partial
def on_event(event_type, data, *, log=False):
if log:
print(f"[{event_type}] {data}")
on_click = partial(on_event, "click", log=True)
on_click("button_submit")
# [click] button_submit
Composing decorators correctly
from functools import wraps
def require_auth(func):
@wraps(func)
def wrapper(request, *args, **kwargs):
if not request.get("user"):
raise PermissionError("Not authenticated")
return func(request, *args, **kwargs)
return wrapper