Back to Blog
Python

Python Weak References: Track Objects Without Blocking Cleanup

python weak references: Python weak references let you track and cache objects without keeping them alive, using weakref.ref, WeakValueDictionary, and WeakKeyDictionary.

weakrefgarbage collectionmemory managementcachingobject lifetime
Illustration of a Python object held by a solid strong reference and a translucent weak reference that fades away, showing the object can be garbage collected.

What a Weak Reference Actually Is

Python weak references let you track or cache objects without keeping them alive. A weak reference is a reference to an object that does not stop the garbage collector from reclaiming that object once all strong references are gone. When the object is collected, the weak reference returns None instead of the object.

This matters in long-running applications where caches, registries, or observer lists can accidentally hold objects in memory far longer than needed. A strong reference in a dictionary value, for example, keeps the object alive for as long as the dictionary exists. A weak reference does not.

The weakref module in the standard library provides the tools for working with weak references.

Creating and Using a Weak Reference

The core API is weakref.ref(). You pass an object to it, and it returns a reference object that you can call like a function to retrieve the original object:

import weakref class Widget: def __init__(self, name): self.name = name widget = Widget("panel") ref = weakref.ref(widget) # Retrieve the object through the weak reference obj = ref() print(obj.name) # panel # Remove the strong reference del widget # The object is now eligible for collection print(ref()) # None

The weak reference object is callable. Calling it returns the referent if it is still alive, or None if it has been collected. Always check the result before using it. In code where the last strong reference can be dropped by another thread or a callback, the referent may disappear between the call and your use of the object.

Which Objects Can Be Weakly Referenced

Not every Python object supports weak references. Instances of most user-defined classes do, but several built-in types do not:

import weakref # This raises TypeError weakref.ref([1, 2, 3])

Lists, dictionaries, strings, integers, tuples, and sets cannot be weakly referenced. If you need to track one of these types, wrap it in a small class or hold it inside a container that supports weak references.

For classes that define __slots__, you must include __weakref__ in the slot list:

class Point: __slots__ = ("x", "y", "__weakref__") def __init__(self, x, y): self.x = x self.y = y

Without __weakref__ in the slots, instances of the class cannot be weakly referenced and weakref.ref() raises TypeError.

WeakValueDictionary for Caches

weakref.WeakValueDictionary is a dictionary whose values are weakly referenced. When a value is garbage collected, its entry is removed from the dictionary automatically. This is the standard tool for building caches that do not keep their contents alive indefinitely:

import weakref class Image: def __init__(self, path): self.path = path cache = weakref.WeakValueDictionary() def load_image(path): image = cache.get(path) if image is None: image = Image(path) cache[path] = image return image

The cache holds the image only while something else in the program also holds a reference to it. Once the last caller releases the image, the entry disappears from the cache. This prevents a cache from becoming a memory leak in a long-running process.

WeakKeyDictionary for Tracking Objects

weakref.WeakKeyDictionary is the inverse: the keys are weakly referenced, and the entry is removed when a key is collected. This is useful when you need to attach metadata to objects without keeping them alive:

import weakref class Session: def __init__(self, user_id): self.user_id = user_id session_metadata = weakref.WeakKeyDictionary() def attach_metadata(session, metadata): session_metadata[session] = metadata

The metadata is removed automatically when the session object is garbage collected. This avoids the common problem of a registry that grows without bound because it holds strong references to keys that no longer exist elsewhere.

WeakSet for Membership Tracking

weakref.WeakSet is a set whose elements are weakly referenced. It is useful for tracking live instances of a class without preventing their collection:

import weakref class Connection: _live = weakref.WeakSet() def __init__(self): Connection._live.add(self) @classmethod def live_count(cls): return len(cls._live)

Each new Connection instance registers itself in the weak set. When an instance is no longer referenced anywhere else, it is removed from the set automatically. This pattern is common for diagnostics, monitoring, and resource cleanup.

Callbacks and Finalization with weakref.finalize

weakref.finalize registers a callback that runs when the referent is garbage collected. This is a more reliable alternative to __del__ because it avoids the problems __del__ has with cyclic references and interpreter shutdown:

import weakref class TempFile: def __init__(self, path): self.path = path self._finalizer = weakref.finalize(self, self._cleanup, path) @staticmethod def _cleanup(path): print(f"Removing {path}")

The callback receives the arguments you pass at registration time, not the object itself. This avoids keeping a strong reference to the object inside the callback, which would defeat the purpose of the finalizer.

Common Pitfalls and Edge Cases

Weak references are not hashable by default. If you need to use them as dictionary keys or store them in a set, you must subclass weakref.ref and provide __hash__ and __eq__, or use the identity of the reference object directly.

A more subtle issue is that the referent can disappear at any time. Code like this is unsafe in a multithreaded program:

obj = ref() if obj is not None: obj.do_something()

Between the call to ref() and the call to do_something(), another thread could drop the last strong reference and the object could be collected. In single-threaded code this is rarely a problem, but in threaded code you should keep a strong reference for the duration of the operation.

Memory and Performance Considerations

Weak references add a small overhead. Each weak reference requires an allocation, and the garbage collector must process the weak reference when the referent is collected. For most applications this cost is negligible.

The bigger concern is correctness. A cache built with WeakValueDictionary can evict entries at any time, so code that assumes a cached value is always present will fail. Always handle the miss case, as in the image loader example above.

Weak references do not help with reference cycles involving __del__, because the garbage collector handles those separately. weakref.finalize works correctly even in the presence of cycles, which is one reason it is preferred over __del__ for cleanup logic.

python weak references: Practical Usage and Code Examples | RYUSLOG DEV