Automatic memory management is one of Python’s greatest conveniences, but ordinary references can keep objects alive longer than an application needs them. A cache, observer registry, or metadata mapping may retain large objects after the rest of the program has stopped using them. The Python weakref module provides weak references: links that can access an object while it exists without preventing garbage collection from reclaiming its memory.
This guide covers strong and weak references, weakref.ref(), WeakValueDictionary, WeakKeyDictionary, WeakSet, WeakMethod, proxies, and finalize(). It complements the guides about Python descriptors, fixing Python MemoryError, the collections module, diagnosing slow Python scripts, and lazy iteration and generator expressions.
Strong references and object lifetime
A normal variable holds a strong reference. As long as at least one strong reference remains, the object stays alive. Variables, attributes, list entries, and dictionary values all commonly create strong references.
class Image:
def __init__(self, name):
self.name = name
image = Image("cover.png")
cache = {"cover": image}
del image
print(cache["cover"].name)Deleting the local variable does not destroy the object because the dictionary still owns it. This is usually correct, but it is undesirable when the mapping is only an optional cache or auxiliary index. A weak reference does not increase the strong-reference count. When only weak references remain, the object becomes eligible for collection.
Creating a reference with weakref.ref
weakref.ref() returns a callable reference object. Calling it returns the referent while it is alive or None after collection.
import weakref
class Document:
pass
document = Document()
reference = weakref.ref(document)
print(reference() is document)
del document
print(reference())The official Python weakref documentation uses the word referent for the object targeted by a weak reference. The key property is that the weak reference does not extend the referent’s lifetime.
The safe dereferencing pattern
Do not test a weak reference and then call it again. In concurrent code, another thread could remove the last strong reference between those operations. Retrieve the object once and keep the local strong reference during use.
obj = reference()
if obj is None:
print("The object is gone")
else:
obj.process()Assigning the result to obj temporarily keeps the referent alive. This pattern is safe in single-threaded and threaded code.
Callbacks after collection
weakref.ref() accepts a callback that runs when the referent is about to be finalized. The callback receives the weak-reference object, not the unavailable referent.
import weakref
class Session:
pass
def removed(reference):
print("Session removed from the index")
session = Session()
reference = weakref.ref(session, removed)
del sessionDo not capture the monitored object through a closure, argument, or bound method. That indirect strong reference would prevent collection. Exceptions raised by weak-reference callbacks are written to standard error but cannot propagate to the code that released the final strong reference.
Automatic caches with WeakValueDictionary
WeakValueDictionary stores ordinary keys and weak values. When a value has no strong references elsewhere, its entry disappears automatically.
from weakref import WeakValueDictionary
class Profile:
def __init__(self, identifier):
self.identifier = identifier
_cache = WeakValueDictionary()
def load_profile(identifier):
profile = _cache.get(identifier)
if profile is None:
profile = Profile(identifier)
_cache[identifier] = profile
return profile
profile = load_profile(42)
print(list(_cache))
del profile
print(list(_cache))This pattern suits expensive objects that can be reconstructed: decoded images, temporary models, resource wrappers, syntax trees, and intermediate representations. The cache is an optimization, not persistent storage, because an entry may vanish whenever no caller owns the object.
When a weak cache is not enough
A weak cache can empty immediately if callers do not retain values. It also provides no maximum size, least-recently-used policy, expiration time, or distributed consistency. functools.lru_cache() may be better for small deterministic results. Databases and external caches are appropriate when data must survive object collection or process restarts.
Choose WeakValueDictionary when the rule is: reuse this object while another part of the program genuinely needs it, but never keep it alive only because it appears in the cache.
External metadata with WeakKeyDictionary
WeakKeyDictionary stores keys weakly. It lets a library associate information with third-party objects without changing those objects and without taking ownership of their lifetime.
from weakref import WeakKeyDictionary
class Connection:
pass
metrics = WeakKeyDictionary()
connection = Connection()
metrics[connection] = {"queries": 3}
print(metrics[connection])
del connection
print(len(metrics))This is valuable for instrumentation, descriptors, frameworks, adapters, and plugins. When the primary instance disappears, its auxiliary metadata is removed as well.
Equality and identity in weak keys
A subtle behavior appears when two different key objects compare equal. Inserting the second key may replace the value without replacing the internally retained identity of the first key. When the original key is collected, the entry can disappear even though the second equal object remains alive.
If distinct but equal instances are common, delete the old entry before assigning the new one or use a stable immutable identifier. Classes that override equality and hashing should include explicit tests for weak-key behavior.
Observer registries with WeakSet
WeakSet implements the set interface while keeping elements weakly. It is a natural fit for observer, listener, and active-object registries.
from weakref import WeakSet
class Observer:
def update(self, event):
print("Event:", event)
observers = WeakSet()
observer = Observer()
observers.add(observer)
for item in list(observers):
item.update("data changed")
del observer
print(len(observers))Iterating over a snapshot can be useful when callbacks may alter the registry. A weak registry prevents the publisher from accidentally becoming the owner of every subscriber.
Bound methods and WeakMethod
A bound method such as instance.refresh is a temporary object created during attribute access. A regular weak reference to it may expire immediately. WeakMethod stores enough information to recreate the bound method while both the instance and original function are alive.
from weakref import WeakMethod
class View:
def refresh(self):
print("Refreshing")
view = View()
callback = WeakMethod(view.refresh)
method = callback()
if method is not None:
method()
del view
print(callback())This is especially helpful in event systems, graphical interfaces, and message buses that should not retain consumers forever.
Weak proxies
weakref.proxy() creates an object that forwards most operations to the referent. It avoids explicit calls to the reference object, but access after collection raises ReferenceError.
import weakref
class Settings:
environment = "production"
settings = Settings()
proxy = weakref.proxy(settings)
print(proxy.environment)
del settings
try:
print(proxy.environment)
except ReferenceError:
print("Settings are unavailable")Proxies are never hashable, even when the referent is. For library APIs, explicit references often make the missing-object state clearer and easier to type-check.
Cleanup with weakref.finalize
weakref.finalize() registers a cleanup callable that runs at most once when an object is collected. The finalizer object remains alive automatically, making it easier to manage than a raw weak-reference callback.
import shutil
import tempfile
import weakref
class TemporaryDirectory:
def __init__(self):
self.path = tempfile.mkdtemp()
self._finalizer = weakref.finalize(
self,
shutil.rmtree,
self.path,
ignore_errors=True,
)
def remove(self):
self._finalizer()
@property
def removed(self):
return not self._finalizer.aliveThe finalizer can be invoked explicitly, and later calls do nothing. By default, remaining live finalizers also run during normal interpreter shutdown in reverse creation order.
Do not retain the monitored object
The function, positional arguments, and keyword arguments passed to finalize() must not own the monitored object directly or indirectly. Passing the instance’s own bound method is a common mistake.
# Avoid:
# weakref.finalize(self, self.close)
# Prefer an external function with only required data:
weakref.finalize(self, close_resource, resource_id)If the finalizer keeps self, the instance owns the finalizer, the finalizer owns the bound method, and the method owns the instance. The object may never become collectible.
Types that support weak references
Instances of normal Python classes generally support weak references. Python functions, methods, sets, generators, sockets, arrays, and many other types also work. Built-in lists and dictionaries do not support them directly.
import weakref
class MyList(list):
pass
values = MyList([1, 2, 3])
reference = weakref.ref(values)
print(reference())Subclasses of list and dict can gain support, but some types such as tuple and int still do not support weak references even when subclassed.
weakref and __slots__
Declaring __slots__ disables automatic weak-reference support unless the slot sequence includes "__weakref__".
import weakref
class User:
__slots__ = ("name", "__weakref__")
def __init__(self, name):
self.name = name
user = User("Ana")
reference = weakref.ref(user)
print(reference().name)This matters in memory-sensitive classes that use slots while also participating in weak caches or registries.
Garbage collection and testing
In CPython, non-cyclic objects are often destroyed immediately when the final strong reference disappears. Other implementations may collect later. The standard gc module documentation explains collection controls and debugging support.
import gc
import weakref
class Item:
pass
item = Item()
reference = weakref.ref(item)
del item
gc.collect()
assert reference() is NoneUse gc.collect() in controlled tests and investigations, not as a routine workaround for unclear object ownership or memory retention.
Weak references and cycles
Python’s cyclic collector already handles many reference cycles. Weak references help when a relationship does not conceptually represent ownership. For example, a child may weakly refer to a parent when the parent already strongly owns the child. That design clarifies which object controls lifetime.
Do not replace every relationship with weak references. At least one component must own an object for as long as it is needed. Otherwise objects may disappear prematurely and produce intermittent failures.
Threading considerations
A weak reference can become dead at any time after the last strong reference is removed. The safe pattern is to call the reference once and use the returned local object. Checking liveness with one call and retrieving with another creates a race.
Weak containers also do not automatically make compound operations atomic. If multiple threads update a registry or cache, use an appropriate lock around the complete read-modify-write sequence.
Common mistakes
- Using a weak cache as persistent storage.
- Keeping only weak references while expecting objects to remain alive.
- Calling a reference twice in concurrent code.
- Capturing the referent inside its callback or finalizer.
- Passing the object’s own bound method to
finalize(). - Forgetting
"__weakref__"in a slotted class. - Expecting direct support from lists, dictionaries, integers, or tuples.
- Ignoring custom equality in
WeakKeyDictionary. - Assuming immediate collection on every Python implementation.
Recommended practices
- Use weak references only for relationships that do not represent ownership.
- Prefer weak containers and
finalize()over low-level callbacks. - Dereference once before using an object.
- Keep cleanup functions external and independent of the monitored instance.
- Document that weak-cache entries may disappear.
- Test behavior after strong references are removed.
- Measure memory before and after adopting weak references.
- Add size limits, expiration, and synchronization when the use case requires them.
Conclusion
Python weakref lets applications observe, index, and reuse objects without taking responsibility for keeping them alive. Raw references provide low-level control, while WeakValueDictionary, WeakKeyDictionary, and WeakSet solve common cache, metadata, and observer problems. WeakMethod supports bound callbacks, and finalize() provides one-time cleanup.
The module works best when ownership is explicit. Strong references should keep objects alive for real business needs; weak references should provide only auxiliary access. With cycle-free callbacks, correct slot support, concurrency-aware dereferencing, and lifecycle tests, weak references can reduce accidental memory retention without making behavior unpredictable.







