Most Python code should use ordinary references. If one object stores another object in an attribute, list, or dictionary, that reference normally means the stored object should remain available for as long as the owner needs it.
Some relationships are different. A cache may want to reuse an object only while another part of the program already owns it. A registry may want to discover live objects without extending their lifetime. An observer table may want to remember listeners without becoming the reason those listeners can never be collected.
These are non-owning relationships. Python’s weakref module exists for them.
The useful mental model is simple:
strong reference -> contributes to keeping an object alive
weak reference -> can observe an object while it is aliveA weak reference is not a replacement for normal ownership. It is a specialized tool for cases where the relationship should disappear naturally when the target object is no longer strongly referenced elsewhere.
Start with the ownership problem
Suppose an application keeps recently constructed document objects in a normal dictionary:
cache = {}
def remember(document):
cache[document.id] = documentThe dictionary now owns strong references to every stored document. Even if the rest of the application stops using one, the cache still keeps it alive.
That may be exactly what you want for a bounded cache with an explicit eviction policy. But it is wrong if the intended rule is:
Reuse the object when somebody else still needs it, but do not keep it alive only because it appeared in this registry.
A weak reference expresses that second rule.
A weak reference does not keep its target alive
The low-level API is weakref.ref:
import weakref
class Document:
pass
document = Document()
reference = weakref.ref(document)
assert reference() is documentCalling the weak-reference object returns the target while the target is still alive.
After the last strong reference disappears, the weak reference eventually stops resolving to the object:
del document
if reference() is None:
print("document is no longer available")The important point is not that reference() behaves like a special nullable pointer. The important point is ownership: reference itself is not enough to keep the Document instance alive.
Do not separate the liveness check from retrieval
It is tempting to write:
if reference() is not None:
reference().process()That calls the weak reference twice. In concurrent code, the object could disappear between those calls.
Retrieve it once instead:
document = reference()
if document is not None:
document.process()The local variable document is now a normal strong reference for the duration of that use, so the object cannot disappear halfway through the operation merely because the weak reference changed state.
Prefer weak containers when the relationship is a collection
Application code often does not need to manage individual weakref.ref objects. The standard library provides collection types that remove entries automatically when their weakly referenced objects disappear.
The most common are:
weakref.WeakValueDictionary, which holds values weakly;weakref.WeakKeyDictionary, which holds keys weakly;weakref.WeakSet, which holds its elements weakly.
These types are usually clearer than building a custom dictionary of raw weak references.
Use WeakValueDictionary for a non-owning registry
A WeakValueDictionary behaves like a mapping whose values do not stay alive solely because the mapping contains them.
import weakref
class Document:
def __init__(self, document_id):
self.document_id = document_id
registry = weakref.WeakValueDictionary()
document = Document("doc-42")
registry[document.document_id] = document
assert registry["doc-42"] is documentAs long as document remains strongly referenced, the registry can find it.
When the last strong reference disappears, the corresponding mapping entry can disappear as well:
del document
assert "doc-42" not in registryThis pattern is useful for identity registries and some caches where keeping an object alive is not part of the cache’s responsibility.
It is not a general replacement for caching. If the cache is supposed to retain expensive results for a defined period, a weak-value cache may discard them sooner than the application expects.
Weak caches trade retention guarantees for lower ownership pressure
A normal cache answers this question:
Should the cache keep this value available for later reuse?
A weak cache answers a different question:
If the value is still alive somewhere else, can we reuse that same object?
Those policies produce different behavior.
Imagine an expensive parsed schema:
normal cache
request ends -> cache still owns schema -> schema remains reusable
weak cache
request ends -> no other strong owner -> schema may disappearA weak cache therefore does not guarantee a hit merely because a key was inserted previously. If deterministic retention matters, use a normal cache with a size, lifetime, or eviction policy you can reason about.
Weak references are best when opportunistic reuse is enough.
Use WeakKeyDictionary for metadata attached externally
Sometimes you want to associate metadata with an object that another part of the program owns.
For example, a library may want to remember when objects were inspected without modifying those objects directly:
import weakref
last_seen = weakref.WeakKeyDictionary()
def mark_seen(obj, timestamp):
last_seen[obj] = timestampThe metadata entry disappears when the key object is no longer strongly referenced elsewhere.
That can be useful for:
- auxiliary metadata managed outside the object;
- memoized information tied to object identity;
- instrumentation that should not extend application object lifetimes.
Equal objects can make WeakKeyDictionary surprising
WeakKeyDictionary is not simply an identity-only table.
If two distinct key objects compare equal and have compatible hashes, inserting the second can replace the value associated with the first key without necessarily replacing the stored key object itself. If the original key later disappears, the entry may disappear too.
That behavior matters for value-like objects whose equality is broader than identity.
When metadata must be tied strictly to a particular object identity, verify that the key type’s equality semantics match the design. Do not assume weak-key storage automatically means identity semantics in every detail.
WeakSet is useful for non-owning membership
A WeakSet is appropriate when you only need to know which live objects belong to a group.
import weakref
class Listener:
pass
listeners = weakref.WeakSet()
listener = Listener()
listeners.add(listener)
assert listener in listenersIf nothing else owns listener, the set does not keep it alive.
This can fit observer-style relationships where the publisher should not own the listener’s lifetime.
However, event systems have more concerns than lifetime alone. You still need to decide how callbacks are invoked, how failures are isolated, whether ordering matters, and whether bound methods are stored correctly.
Bound methods need special care
A bound method such as service.handle is a temporary object created from two pieces: the instance and the underlying function.
A plain weak reference to a bound method is often not useful because that temporary bound-method object may disappear immediately even while the instance itself is still alive.
Python provides weakref.WeakMethod for this case:
import weakref
class Service:
def handle(self):
return "handled"
service = Service()
callback = weakref.WeakMethod(service.handle)
method = callback()
assert method is not None
assert method() == "handled"WeakMethod can recreate the bound method while both the instance and original function still exist.
This is useful when implementing callback registries that should not own subscriber instances.
Not every Python object supports weak references
Weak referencing is a capability of the target object’s type.
Instances of ordinary user-defined classes generally support weak references. Many built-in and extension types support them too, but support is not universal.
For example, a plain list cannot be weakly referenced directly:
import weakref
values = []
weakref.ref(values) # raises TypeErrorDo not infer weak-reference support from whether an object is mutable, hashable, or otherwise container-like. Those are separate properties.
If your design accepts arbitrary user objects, be prepared for TypeError when weak-reference support is not guaranteed by the interface.
slots can disable weak-reference support
A user-defined class normally gets weak-reference support automatically. Defining __slots__ changes that unless the class hierarchy still provides a weak-reference slot.
This class does not support weak references:
class Point:
__slots__ = ("x", "y")
def __init__(self, x, y):
self.x = x
self.y = yIf instances need to be weakly referenceable, include __weakref__:
class Point:
__slots__ = ("x", "y", "__weakref__")
def __init__(self, x, y):
self.x = x
self.y = yThis is easy to miss when a class is later optimized with slots and code elsewhere already depends on weak references.
Treat weak-reference support as part of the class’s behavioral contract when other components rely on it.
Use finalize when cleanup should follow object lifetime
weakref.finalize registers a callback that can run when an object is collected without requiring the class to implement __del__.
A small example:
import weakref
class ResourceOwner:
pass
owner = ResourceOwner()
finalizer = weakref.finalize(owner, print, "owner released")
assert finalizer.aliveA finalizer runs at most once. It can also be invoked explicitly, which marks it as no longer alive.
This can be useful as a fallback cleanup mechanism, especially when you do not control the target class.
Do not make the finalizer keep the target alive
The cleanup function and its arguments must not strongly reference the target object, directly or indirectly.
This is risky:
class ResourceOwner:
def close(self):
pass
owner = ResourceOwner()
weakref.finalize(owner, owner.close)The bound method owner.close contains a strong reference to owner. The finalizer now helps keep alive the very object whose disappearance is supposed to trigger it.
Pass only the independent cleanup state that is required:
weakref.finalize(owner, close_resource, resource_id)The cleanup callback can then release the external resource without owning the target object.
Finalizers are a fallback, not deterministic resource management
Garbage collection is not a substitute for explicit lifetime boundaries.
If a file, transaction, lock, socket, or temporary directory must be released at a predictable point, prefer deterministic mechanisms such as:
- a
withstatement and context manager; - an explicit
close()or equivalent lifecycle method; try/finallyaround ownership that cannot use a context manager.
A finalizer is more appropriate as a safety net for cleanup that may otherwise be missed.
The distinction matters because object collection timing is not an application-level scheduling guarantee. Code should not depend on a finalizer running immediately after the last obvious use of an object.
Weak references do not solve reference cycles by themselves
Weak references can prevent a particular relationship from becoming a strong ownership edge, which can help avoid cycles in some designs.
But they are not a general instruction to replace one side of every object graph with weak references.
Python’s garbage collector can detect many unreachable reference cycles. More importantly, the correct design question is still ownership:
Which object is responsible for keeping which other object alive?
Use a weak reference when the answer is this relationship should not own the target. Do not use one merely because two objects reference each other.
Avoid depending on exact collection timing
Examples often use del and then immediately show a weak mapping becoming empty. That is convenient for explanation, but application logic should not require one exact collection schedule across Python implementations.
del name removes a reference from that name. It does not itself promise that the target object has been destroyed at that exact line.
For this reason, avoid designs where correctness depends on a weak-container entry disappearing immediately. The container should be treated as a view of objects that are currently reachable strongly elsewhere, not as a deterministic lifecycle notification system.
If you need a reliable state transition, represent that transition explicitly in application logic.
Common mistakes
Using weak references when ownership is actually required
If an object must remain available until a job completes, configuration reloads, or a cache entry expires, keep a strong reference for that required lifetime.
Treating a weak cache as a normal cache with automatic memory management
Weak values may disappear whenever no other strong references remain. That policy can cause recomputation and unpredictable hit rates if you expected the cache itself to retain entries.
Assuming every object can be weakly referenced
Support depends on the target type. Validate the contract or handle TypeError when arbitrary objects are accepted.
Storing a bound method in a finalizer for its own instance
That bound method strongly references the instance and can prevent the desired lifecycle behavior.
Calling a weak reference repeatedly during one operation
Resolve it once, check for None, and hold the returned strong reference while using the object.
When weak references are the right tool
Weak references are a good fit when all of the following are true:
- another part of the program is the real owner of the object;
- your component only wants to observe, index, annotate, or opportunistically reuse that object;
- your component should naturally forget the relationship when the real owners disappear;
- losing the weakly held entry is acceptable behavior rather than a correctness failure.
If any of those conditions is false, a normal strong reference is usually clearer.
Keep ownership explicit
Weak references are easiest to use correctly when they are viewed as an ownership tool rather than a memory trick.
Use strong references for objects your component is responsible for keeping alive. Use WeakValueDictionary, WeakKeyDictionary, WeakSet, or WeakMethod when a relationship should exist only while another owner keeps the target alive. Use finalize as a carefully designed fallback for lifecycle cleanup, not as a replacement for deterministic resource management.
That distinction keeps caches, registries, observers, and metadata tables from silently becoming object owners. More importantly, it makes lifetime behavior understandable from the design instead of leaving it to accidental reference chains.