How to Use a Weakref Cache to Avoid Memory Leaks in Python
This code demonstrates building a value cache with weakref.WeakValueDictionary so objects can be garbage collected when no longer referenced, preventing memory leaks.
Python code
43 linesimport weakref
import gc
class ExpensiveObject:
def __init__(self, name):
self.name = name
def __repr__(self):
return f"ExpensiveObject('{self.name}')"
class ObjectCache:
def __init__(self):
self._cache = weakref.WeakValueDictionary()
def get_or_create(self, name):
obj = self._cache.get(name)
if obj is None:
obj = ExpensiveObject(name)
self._cache[name] = obj
return obj
def __len__(self):
return len(self._cache)
if __name__ == "__main__":
cache = ObjectCache()
obj1 = cache.get_or_create("alpha")
obj2 = cache.get_or_create("alpha")
print(f"Same object: {obj1 is obj2}")
print(f"Cache size with reference: {len(cache)}")
# Drop the only strong reference
del obj1
del obj2
gc.collect()
print(f"Cache size after GC: {len(cache)}")
print("Memory leak avoided via weakref cache")
Output
Same object: True
Cache size with reference: 1
Cache size after GC: 0
Memory leak avoided via weakref cache
How it works
A WeakValueDictionary stores weak references to values, so when all strong references to an object are dropped, the object can be garbage collected and automatically removed from the cache. In get_or_create, we check the cache first and only construct a new ExpensiveObject if the key is missing, ensuring reuse while the object is alive. The gc.collect() call forces garbage collection to demonstrate that the cache does not keep objects alive. This pattern gives you the performance benefit of caching without the memory leak that a regular dictionary would cause.
Common mistakes
- Using a regular dictionary instead of WeakValueDictionary, which pins objects in memory.
- Forgetting that WeakValueDictionary only supports weakref-able value types; use WeakKeyDictionary for key-based caching.
- Expecting the cache to retain values that have no other strong references.
- Not calling `gc.collect()` in tests to see the effect if the interpreter doesn't collect immediately.
Variations
- Use `functools.lru_cache` for memoization with size limits instead of manual caching.
- Use a `WeakKeyDictionary` when you want weak keys and strong values.
Real-world use cases
- Caching database query results so that removing references frees memory automatically.
- Storing loaded configuration or connection objects that should vanish when their owners are done.
- Implementing a resource pool for expensive objects like model instances without leaking memory.
Sponsored
More from Concurrency & performance
- Benchmark list.append vs deque.append in Python medium
- Build a Python Performance Profiler That Generates Readable Reports medium
- Graceful Shutdown Executor Context Manager in Python medium
- How to Build a Producer-Consumer Pattern with asyncio.Queue in Python medium
- How to Cancel an asyncio Task with Graceful Cleanup in Python medium
- How to Convert Data in Parallel with ThreadPoolExecutor in Python easy
Keep learning
Related tutorials and quizzes for this topic.