What is `weakref`, and when would you use it?
Quick Answer
`weakref.ref(obj)` creates a reference to `obj` that **doesn't increment its refcount** — so it doesn't keep the object alive, and calling the weak reference returns `None` once the object is actually garbage collected. It's used to avoid reference cycles (e.g., parent/child back-pointers) and to build caches/registries that shouldn't themselves prevent an object from being freed when nothing else needs it.
Detailed Answer
Basic usage: a reference that doesn't keep the object alive
import weakref
class Resource:
def __init__(self, name):
self.name = name
r = Resource("db-connection")
weak = weakref.ref(r)
weak() # <Resource object> -- still alive, call it to get the real object (or None)
del r # the only STRONG reference is gone
weak() # None -- the object was actually collected; weakref doesn't keep it alive
Unlike a normal reference (weak = r, which increments the refcount),
weakref.ref(r) doesn't — so it has no say in whether r stays alive.
Calling weak() returns the live object while it exists, or None once
it's actually been collected.
Use case 1: caches that don't prevent eviction
import weakref
_cache = weakref.WeakValueDictionary()
def get_resource(key):
resource = _cache.get(key)
if resource is None:
resource = load_expensive_resource(key)
_cache[key] = resource
return resource
WeakValueDictionary holds weak references to its values — entries are
automatically removed once nothing else references the value. This gives
you caching ("reuse the object if something else is still using it")
without the cache itself becoming the reason large objects never get
freed (a plain dict-based cache would keep every entry alive forever,
a common source of memory leaks in long-running processes).
Use case 2: breaking reference cycles (parent/child back-pointers)
class TreeNode:
def __init__(self, value):
self.value = value
self.children = []
self._parent_ref = None
@property
def parent(self):
return self._parent_ref() if self._parent_ref else None
def add_child(self, child):
child._parent_ref = weakref.ref(self)
self.children.append(child)
The child's reference to its parent doesn't keep the parent alive — only
the parent's children list (a strong reference, appropriately, since a
parent should keep its children alive) does. This avoids creating a cycle
at all, so refcounting alone can free the tree immediately once the root
goes out of scope, rather than waiting for a periodic GC cycle pass.
Use case 3: observer patterns
class EventBus:
def __init__(self):
self._listeners = weakref.WeakSet()
def subscribe(self, listener):
self._listeners.add(listener)
Listeners registered with the bus don't have their lifetime extended just
because they subscribed — if a listener object is otherwise no longer
needed, it can still be garbage collected, and the WeakSet
automatically drops the now-dead reference rather than leaking it
indefinitely.
The limitation: not every object supports weak references
weakref.ref(42) # TypeError: cannot create weak reference to 'int' object
Some built-in types (int, str, tuple in some cases) don't support
weak references directly without wrapping — mainly relevant for custom
classes (which support it by default unless __slots__ excludes
__weakref__) and specific container types designed for this purpose
(WeakValueDictionary, WeakKeyDictionary, WeakSet).
Interview-ready summary: weakref creates references that don't
extend an object's lifetime, used for caches that shouldn't prevent
eviction (WeakValueDictionary) and for breaking reference cycles in
back-pointer relationships (parent/child, observer patterns) — letting
reference counting reclaim memory immediately instead of relying on the
periodic cyclic garbage collector.