The weakref module creates references that point to an object without preventing garbage collection. A normal reference keeps an object alive while at least one strong link exists. A weak reference can be queried while the object is alive, but it does not increase ownership or artificially extend the object’s lifetime.
This is useful for caches, object registries, metadata maps, observers, component trees, and other structures that should not own the objects they track. Weak references can also reduce accidental retention, but they do not replace a clear ownership model. The design must define when an object may disappear and how callers respond.
Strong and weak references
An ordinary variable creates a strong reference.
obj = MyClass()
alias = obj
As long as obj or alias exists, the instance remains reachable. A weakref.ref() can become empty after every strong reference is removed.
import weakref
obj = MyClass()
reference = weakref.ref(obj)
print(reference() is obj)
del obj
print(reference())
Calling reference() returns the live object or None after collection.
Not every object supports weak references
Instances of user-defined classes normally support weak references. Many built-in types, including plain list and dict objects, do not support them directly, although selected subclasses can.
import weakref
try:
weakref.ref([])
except TypeError as error:
print(error)
A generic API should handle TypeError or document that supplied objects must be weak-referenceable.
Classes that use __slots__
When a class defines __slots__, include "__weakref__" to permit weak references.
class User:
__slots__ = ("name", "__weakref__")
def __init__(self, name):
self.name = name
Without that special slot, weakref.ref(instance) raises TypeError. This affects the class layout and should be considered before publishing the API.
Avoid check-then-use races
Do not test a weak reference and call it again later. In concurrent code, the object can disappear between those operations.
obj = reference()
if obj is not None:
obj.run()
The local variable creates a temporary strong reference while the object is being used.
Reference callbacks
weakref.ref() may receive a callback invoked when the referenced object is about to disappear.
import weakref
def removed(reference):
print("object was collected")
obj = MyClass()
reference = weakref.ref(obj, removed)
The callback receives the weak reference itself, not the original object. At that point the target normally cannot be recovered.
Do not capture the target in the callback
A closure that refers strongly to the target defeats the weak reference.
def make_callback(obj):
def callback(reference):
print(obj)
return callback
This keeps the object alive. Capture only independent identifiers, strings, counters, or metadata.
weakref.proxy
weakref.proxy() creates an object that forwards operations to the target without requiring an explicit () call.
import weakref
obj = MyClass()
proxy = weakref.proxy(obj)
proxy.run()
If the target has already been collected, the proxy raises ReferenceError. Use a proxy only when transparent syntax improves the API. A regular ref() makes possible absence more visible.
Callable proxies
Functions and callable instances can produce a CallableProxyType. The proxy still does not own its target.
Do not store a proxy where guaranteed availability is required. ReferenceError must be treated as a normal lifecycle outcome.
WeakValueDictionary
WeakValueDictionary keeps strong keys and weak values. When no strong references to a value remain, its entry disappears automatically.
import weakref
cache = weakref.WeakValueDictionary()
obj = MyClass()
cache["main"] = obj
print("main" in cache)
del obj
print("main" in cache)
It is useful for interning, factories, identity maps, and caches that should not retain results forever.
A weak cache does not guarantee retention
An entry may disappear immediately if no caller keeps a strong reference.
cache[key] = build()
If build() returns an object with no other owner, the value can vanish as soon as the statement completes. A caller that needs it must keep a local strong reference.
WeakKeyDictionary
WeakKeyDictionary stores weak keys and strong values. When a key disappears, the association is removed.
import weakref
metadata = weakref.WeakKeyDictionary()
user = User("Ana")
metadata[user] = {"visits": 1}
This is a convenient way to attach auxiliary information to objects without modifying their classes.
Identity, equality, and keys
Different objects that compare equal can produce surprising behavior in weak key mappings. Hashing and equality determine dictionary behavior, while removal depends on the lifetime of a specific key object.
Prefer keys with stable identity and equality. Never mutate fields used by __hash__ after insertion.
WeakSet
WeakSet stores objects weakly and removes collected members.
import weakref
observers = weakref.WeakSet()
observers.add(listener)
for observer in list(observers):
observer.update()
Creating a list snapshot can be helpful if callbacks modify the set during iteration.
Observer registries without leaks
An event system that keeps listeners in a normal list can accidentally keep screens, controllers, or components alive. A WeakSet reduces that risk for object-based listeners.
The design still needs rules for no-listener cases and for functions, methods, and closures.
WeakMethod
A bound method is temporary because each access to obj.method creates a new method object. WeakMethod stores a recoverable weak reference to the instance-function pair.
import weakref
reference = weakref.WeakMethod(obj.process)
method = reference()
if method is not None:
method()
This is especially useful in callback registries for object-oriented applications.
finalize
weakref.finalize() registers a function that runs when an object is collected.
import weakref
resource = Resource()
finalizer = weakref.finalize(resource, close_handle, resource.handle)
The finalizer remains alive until it runs or is cancelled. It is often more convenient and robust than a direct ref() callback.
Finalization should not be the primary cleanup path
Deterministic release should use with, close(), or another explicit API. Garbage collection may happen late, in an unexpected order, or during interpreter shutdown.
Use finalize as a safety net, not for transactions, commits, critical flushes, or deadlines.
alive, detach, and explicit execution
A finalizer has an alive property. detach() removes and returns the registered information without executing it. Calling the finalizer directly executes it at most once.
if finalizer.alive:
finalizer()
This supports explicit cleanup while preserving protection against duplicate execution.
Finalization order
During interpreter shutdown, remaining finalizers may run according to implementation rules, often in reverse creation order. Do not build a complex dependency graph around that behavior.
A finalizer should not depend on global modules that may already be partially torn down. Pass the required function and simple values directly.
Reference cycles
Weak references can express non-owning edges in an object graph. A child can keep a weak link to its parent when the parent already owns the child strongly.
Modern garbage collectors can resolve many cycles, but weak references still clarify ownership and prevent retention by external registries.
Collection timing varies
CPython reference counting often releases objects quickly. Other implementations may collect later. Even in CPython, cyclic objects depend on the cyclic collector.
Do not write production logic that requires a callback immediately after del. Tests can call gc.collect() in controlled situations, but applications should not rely on it for correctness.
Threads and synchronization
Weak containers do not make compound operations atomic. A target may disappear between observations, and a container may change during iteration.
Use locks when several threads update the same registry. Resolve a weak reference once and keep the local strong reference for the entire operation.
Async tasks
Background tasks whose execution must continue should be kept by strong references according to the event-loop API in use. A weak container is not a reliable task supervisor.
Store tasks in a strong set and remove them with a completion callback when they finish.
Performance and complexity
Weak references add helper objects, callbacks, and container maintenance. They also make lifecycle behavior less obvious to readers.
Use them when they solve a real ownership or cache problem. A normal list with explicit deregistration can be simpler and faster.
Debugging helpers
weakref.getweakrefcount(obj) returns the number of weak references and proxies. getweakrefs() returns the corresponding objects.
print(weakref.getweakrefcount(obj))
print(weakref.getweakrefs(obj))
These functions are useful for diagnostics, although the state may change immediately in concurrent programs.
Testing strategy
Test a live object, collection after the final strong reference is removed, one-time callback execution, explicit finalization, cancellation, unsupported objects, slotted classes, concurrency, and cache entries that disappear earlier than expected.
Avoid assertions about the global collection order of unrelated objects.
Common mistakes
Common failures include using a weak cache when retention is required, capturing the target in its callback, reusing a proxy after collection, forgetting __weakref__ in slots, expecting immediate garbage collection, using finalizers for critical commits, and failing to keep strong references to callbacks or tasks that must remain alive.
Conclusion
weakref models non-owning relationships and builds caches that release entries automatically when objects are no longer used. Use ref for explicit access, proxies for transparent syntax, weak dictionaries and sets for registries, and finalize as an additional cleanup safeguard.
Keep primary ownership explicit, accept that the target can disappear, and prefer context managers for deterministic release. Consult the official weakref documentation and Python contextlib.







