Python functools: lru_cache, partial, reduce, singledispatch
Imagine your functions could remember past answers, come pre-loaded with some arguments, or automatically handle different data types. That's what the `functools` module gives you: power-ups for your functions.
In this tutorial, you'll learn five tools from functools: lru_cache for memoization (remembering results), partial for pre-filling arguments, reduce for combining a sequence into a single value, singledispatch for handling different types, and total_ordering for easy comparison methods.
These tools are used heavily in real-world Python. Web frameworks use lru_cache to speed up expensive lookups. CLI tools use partial to simplify callback functions. Data pipelines use reduce to aggregate results.
How Does lru_cache Speed Up Slow Functions?
Imagine you're a librarian and someone asks for the same book five times in a row. Instead of walking to the shelf each time, you'd keep it on your desk. That's memoization: remembering the result of a function call so you don't have to compute it again.
lru_cache is a decorator that automatically caches the results of a function. "LRU" stands for "Least Recently Used," which means when the cache is full, it throws out the results that haven't been used in the longest time.
You can set maxsize=None for an unlimited cache, or use a number to limit memory usage. You can also clear the cache with .cache_clear().
What Does partial Do and When Should You Use It?
Imagine a pizza ordering function that takes size, crust, and toppings. If you always order large thin-crust, it's tedious to type those every time. partial lets you create a new function with some arguments already filled in.
This is especially useful when you need to pass a function as a callback but it requires extra arguments. Instead of writing a lambda every time, partial creates a clean, reusable function.
numbers = [1, 2, 3, 4, 5]
result = list(map(lambda x: x * 10, numbers))
print(result)from functools import partial
import operator
numbers = [1, 2, 3, 4, 5]
multiply_by_10 = partial(operator.mul, 10)
result = list(map(multiply_by_10, numbers))
print(result)How Does reduce Combine a Sequence into One Value?
Think of reduce like a snowball rolling downhill. It starts with the first two items, combines them into one, then combines that result with the next item, and so on until you have a single value.
You can also pass an initial value as the third argument. This is the starting point for the accumulation. It's especially useful when the sequence might be empty.
How Does singledispatch Enable Function Overloading?
In many programming languages, you can have multiple functions with the same name that handle different types of input. Python doesn't normally support this, but singledispatch gives you a clean way to do it.
You write a base function, then register specialized versions for specific types. Python automatically calls the right version based on the type of the first argument.
How Does total_ordering Simplify Comparison Methods?
When you create a class that you want to sort or compare, Python needs you to define up to six comparison methods: __eq__, __lt__, __le__, __gt__, __ge__, and __ne__. That's a lot of repetitive code.
The @total_ordering decorator lets you define just __eq__ and ONE other comparison method (like __lt__), and it automatically fills in the rest for you.
Practice Exercises
Write a recursive factorial(n) function decorated with @lru_cache(maxsize=None). It should return 1 if n <= 1, otherwise n * factorial(n - 1). Print factorial(10) and then print the cache info.
Given the function calculate(amount, rate) that returns amount * rate / 100, use partial to create a tax_15 function that always uses rate=15. Print tax_15(200) and tax_15(500).
Use reduce with a lambda to find the maximum value in [34, 12, 89, 45, 23, 67]. Print the result. Your lambda should compare two values and return the larger one.
What does this code print? Think about which argument gets pre-filled.
from functools import partial
def add(a, b, c):
return a + b + c
add_10 = partial(add, 10)
result = add_10(20, 30)
print(result)Use reduce to join the list ['Python', 'is', 'awesome'] into a single string with spaces between words. Use a lambda that concatenates two strings with a space. Print the result.
Create a format_value function using @singledispatch. The base function should print Value: <value>. Register a version for int that prints Integer: <value> and one for list that prints List with <len> items. Call it with 42, [1, 2, 3], and 'hello'.
This code tries to cache an add function, but it passes a list as an argument, which is not hashable. Fix it by converting the list to a tuple before passing it to the cached function.
Expected output: 6