lru_cache and Partial Functions
lru_cache and Partial Functions
In production Python engineering, optimizing CPU-bound functions and simplifying callable interfaces are common architectural requirements. The functools module provides two essential tools for these tasks: @lru_cache (and @cache) for memoization, and partial (and partialmethod) for partial function application.
1. Memoization with @functools.lru_cache
Memoization caches the output of an expensive pure function based on its input arguments. When invoked with previously seen arguments, the function skips execution and returns the cached result from an internal lookup table.
The Least Recently Used (LRU) Eviction Policy
When the cache reaches its maxsize threshold, the entry that has gone the longest without being accessed is evicted to free space for the incoming result.
Python 3.9+ @functools.cache
Python 3.9 introduced @functools.cache as a shorthand for @functools.lru_cache(maxsize=None). Because it is unbounded, it avoids LRU eviction tracking overhead, providing maximum execution speed when memory growth is not a concern.
2. Hashability and the Method Caching Trap
@lru_cache decorated function must be hashable (i.e. implement __hash__). Passing a list, dict, or set will trigger a runtime TypeError: unhashable type: 'list'.self): If you apply @lru_cache directly to an instance method, the cache table stores a reference to self in its key tuple (self, *args). This circular reference prevents the instance from being garbage-collected until cache_clear() is explicitly invoked!3. Partial Function Application with functools.partial
Partial function application allows you to "freeze" a portion of a function's arguments and/or keyword arguments, producing a new callable with fewer required parameters (reduced arity).
Visual Architecture & Process Flow
How data and code flow step-by-step
4. functools.partialmethod for Class Descriptors
When working with methods within class bodies, functools.partial fails to bind self correctly when invoked as an instance method. Python provides functools.partialmethod specifically designed to respect descriptor binding:
Visual Architecture & Process Flow
How data and code flow step-by-step
5. Architectural Comparison Summary
| Feature | lru_cache / cache | partial / partialmethod |
|---|---|---|
| Primary Goal | Execution acceleration via memoization | Interface specialization and arity reduction |
| Key Constraint | Arguments must be immutable and hashable | Order of positional arguments must be preserved |
| Inspection Tool | .cache_info(), .cache_clear() | .func, .args, .keywords |
| Method Variant | @cached_property for instances | partialmethod for descriptors |
Multiple Choice Questions
1.
What exception is raised when passing a mutable list into a function decorated with @functools.lru_cache? A. ValueError B. TypeError: unhashable type: 'list' C. KeyError D. MemoryError
@functools.lru_cache uses the function's arguments as keys in an internal hash table. Because lists are mutable, they do not implement __hash__ and trigger a TypeError.2.
What does the hits metric reported by my_func.cache_info() signify? A. The number of errors caught by the cache. B. The number of times the function returned a cached result without executing the underlying function body. C. The number of active threads accessing the cache. D. The number of evicted keys.
3.
What is the key difference between @functools.lru_cache(maxsize=None) and @functools.cache? A. @functools.cache is slower because it writes to disk. B. @functools.cache is an alias introduced in Python 3.9 specifically for an unbounded cache (maxsize=None), running slightly faster by bypassing LRU eviction logic. C. @functools.cache works with unhashable types. D. @functools.cache clears itself every 60 seconds.
@functools.cache provides an unbounded memoization decorator with less bookkeeping overhead than lru_cache with a capacity limit.4.
What does functools.partial produce when called? A. A generator that yields each parameter. B. A new callable partial object with fixed positional and keyword arguments. C. A compiled C-extension module. D. A class definition inheriting from object.
functools.partial returns a partial callable object that wraps the original function with pre-filled arguments and keyword arguments.5.
Why should functools.partialmethod be preferred over functools.partial when defining methods inside a class definition? A. partial runs in a separate thread and causes race conditions. B. partialmethod properly binds the instance (self) when called as a descriptor on an instance, whereas partial treats self as a standard argument. C. partialmethod only accepts integer arguments. D. partial is deprecated in modern Python.
partial does not implement descriptor binding protocol. partialmethod is specifically designed for class definitions so that the instance self is properly bound when the method is invoked on an instance.Project: Data Pipeline with Itertools
Continue learning with hands-on practice, examples, and exercises in the upcoming topic.
Related Lessons
| Previous Lesson | Next Lesson |
|---|---|
| functools for Higher-Order Functions | Project: Data Pipeline with Itertools |
Practice Quiz
Test your understanding of this lesson with 5 questions. Each question has one correct answer.