The weakref module creates references that don't prevent garbage collection. Essential for caches, observers, and avoiding reference cycles.
The Problem
Normal references keep objects alive:
class BigObject:
def __init__(self, name):
self.name = name
self.data = [0] * 1000000
cache = {}
def get_object(name):
if name not in cache:
cache[name] = BigObject(name)
return cache[name]
# Objects stay in memory forever, even if unused
obj = get_object("big")
del obj # Still in cache, never freedWeak References
Weak references don't prevent garbage collection:
import weakref
class BigObject:
def __init__(self, name):
self.name = name
obj = BigObject("big")
weak_ref = weakref.ref(obj)
# Access the object
print(weak_ref()) # <BigObject object at ...>
# Delete the strong reference
del obj
# Weak ref now returns None
print(weak_ref()) # NoneWeakValueDictionary
Cache that automatically removes garbage-collected values:
import weakref
class BigObject:
def __init__(self, name):
self.name = name
self.data = [0] * 1000000
cache = weakref.WeakValueDictionary()
def get_object(name):
obj = cache.get(name)
if obj is None:
obj = BigObject(name)
cache[name] = obj
return obj
# Object cached while in use
obj = get_object("big")
print("big" in cache) # True
# When no strong references remain, it's removed
del obj
# cache["big"] is now gone (after GC)WeakKeyDictionary
Store metadata about objects without keeping them alive:
import weakref
# Track extra data about objects
metadata = weakref.WeakKeyDictionary()
class User:
def __init__(self, name):
self.name = name
user = User("Alice")
metadata[user] = {"login_count": 5, "last_seen": "2024-01-01"}
# Access metadata
print(metadata[user]["login_count"])
# When user is deleted, metadata is automatically cleaned up
del user
# metadata entry is goneWeakSet
A set that doesn't prevent garbage collection:
import weakref
class Observer:
def notify(self, message):
print(f"Received: {message}")
observers = weakref.WeakSet()
def add_observer(obs):
observers.add(obs)
def notify_all(message):
for obs in observers:
obs.notify(message)
obs1 = Observer()
obs2 = Observer()
add_observer(obs1)
add_observer(obs2)
notify_all("Hello") # Both receive
del obs1
notify_all("World") # Only obs2 receivesCallbacks on Collection
Run code when an object is garbage collected:
import weakref
def cleanup(ref):
print("Object was garbage collected")
obj = object()
weak_ref = weakref.ref(obj, cleanup)
del obj
# Prints: "Object was garbage collected"Finalize
More robust cleanup with finalize:
import weakref
import tempfile
import os
class TempFileHandler:
def __init__(self, content):
self.path = tempfile.mktemp()
with open(self.path, 'w') as f:
f.write(content)
# Register cleanup
self._finalizer = weakref.finalize(
self,
os.unlink,
self.path
)
def read(self):
with open(self.path) as f:
return f.read()
# Temp file is automatically deleted when handler is garbage collected
handler = TempFileHandler("test data")
print(handler.read())
del handler
# File is deletedPractical Example: Object Cache with Expiry
import weakref
import time
class CachedObject:
def __init__(self, key, data):
self.key = key
self.data = data
self.created = time.time()
class SmartCache:
def __init__(self, ttl_seconds=60):
self._cache = weakref.WeakValueDictionary()
self._ttl = ttl_seconds
def get(self, key, factory):
obj = self._cache.get(key)
if obj is None or (time.time() - obj.created) > self._ttl:
obj = CachedObject(key, factory())
self._cache[key] = obj
return obj.data
def __len__(self):
return len(self._cache)
# Usage
cache = SmartCache(ttl_seconds=30)
def expensive_computation():
return {"result": 42}
result = cache.get("key1", expensive_computation)When to Use Weak References
| Use Case | Tool |
|---|---|
| Cache values | WeakValueDictionary |
| Metadata about objects | WeakKeyDictionary |
| Observer pattern | WeakSet |
| Cleanup on GC | finalize |
| Circular reference breaking | weakref.ref |
Limitations
Not all objects support weak references:
import weakref
# These work
weakref.ref(object())
weakref.ref([1, 2, 3]) # ERROR: list doesn't support weakrefs
# Built-in types without __weakref__ slot
# - int, str, tuple, list, dict (the types themselves)
# - But subclasses can add supportQuick Reference
import weakref
# Create weak reference
ref = weakref.ref(obj)
ref() # Get object or None
# Callback on collection
ref = weakref.ref(obj, callback)
# Weak dict (values don't prevent GC)
d = weakref.WeakValueDictionary()
# Weak dict (keys don't prevent GC)
d = weakref.WeakKeyDictionary()
# Weak set
s = weakref.WeakSet()
# Cleanup finalizer
fin = weakref.finalize(obj, cleanup_func, *args)Weak references solve a specific problem: holding references without ownership. When you need a cache that doesn't cause memory leaks, reach for weakref.