Python weakref: When and How to Use Weak References
python weakref: Learn how Python weakref works, when to use weak references, and how WeakValueDictionary, WeakKeyDictionary, and callbacks help manage object lifetimes...
When you keep a reference to an object, Python's garbage collector cannot free that object's memory. That is usually the behavior you want, but sometimes you need to observe an object without keeping it alive. The python weakref module provides weak references that do not prevent the object from being collected. This is useful for caches, registries, and any situation where you want to associate data with an object without extending its lifetime.
Why Python Needs Weak References
A normal assignment creates a strong reference. As long as a strong reference exists, the object stays in memory. Consider a cache that stores computed results keyed by a large object. If the cache holds a strong reference to the key, the key object will never be garbage collected, even after the rest of the program has stopped using it. Over time, this can turn a cache into a memory leak.
Weak references break that cycle. A weak reference lets you access an object if it is still alive, but it does not keep the object alive. When the last strong reference disappears, the object is finalized and the weak reference automatically becomes empty. The weakref module provides the tools to build such references safely.
Creating a Weak Reference with weakref.ref
The core function is weakref.ref. It takes an object and returns a callable that you can invoke to retrieve the original object. If the object is still alive, the call returns it; otherwise, it returns None.
import weakref class Widget: pass widget = Widget() ref = weakref.ref(widget) print(ref() is widget) # True del widget print(ref()) # None
After del widget, the only remaining reference is the weak reference itself, so the Widget instance is collected and ref() returns None. This is the fundamental pattern: you can check whether an object still exists without preventing its destruction.
A weak reference is not a strong reference. You cannot use it directly as the object. To call methods or access attributes, you must first call the reference and then operate on the returned object. This adds a small amount of overhead but keeps the semantics clear.
Using Callbacks to Detect Object Destruction
The weakref.ref constructor accepts an optional callback. The callback is invoked when the object is about to be finalized, before the weak reference becomes empty. This is useful for cleanup operations, such as removing an entry from a registry or invalidating a derived cache.
import weakref class Resource: pass def on_finalize(resource_ref): print(f"Resource {resource_ref} is being collected") resource = Resource() ref = weakref.ref(resource, on_finalize) del resource # prints: Resource <weakref at 0x...> is being collected
The callback receives the weak reference object itself, not the original object. This is because the original object is already gone or in the process of being destroyed. The callback runs on the thread that triggered the garbage collection, so it should not perform blocking operations or assume the object is still usable.
Callbacks are commonly used with WeakValueDictionary and WeakKeyDictionary to automatically clean up entries when the referenced object is collected.
WeakValueDictionary and WeakKeyDictionary for Caches
weakref.WeakValueDictionary is a dictionary that holds weak references to its values. When a value object is garbage collected, its key is automatically removed from the dictionary. This is ideal for caches where the cached result should not keep the key alive.
import weakref class ExpensiveObject: pass cache = weakref.WeakValueDictionary() obj = ExpensiveObject() cache["primary"] = obj print(cache["primary"]) # <__main__.ExpensiveObject object at 0x...> del obj print(cache.get("primary")) # None
Here, the cache does not prevent obj from being collected. Once all strong references to obj are gone, the entry disappears. This prevents the cache from growing indefinitely when the keys are no longer used elsewhere.
weakref.WeakKeyDictionary works the opposite way: it holds weak references to the keys. The value is strongly referenced. This is useful when you want to associate metadata with objects without keeping those objects alive. For example, you might store per-object configuration that should vanish when the object itself is no longer needed.
import weakref class Session: pass metadata = weakref.WeakKeyDictionary() session = Session() metadata[session] = {"user": "alice"} print(metadata[session]) # {'user': 'alice'} del session print(len(metadata)) # 0
Both dictionary types are implemented in pure Python and rely on weak references internally. They are thread-safe only if you protect them with a lock, just like a regular dictionary.
weakref.proxy and Its Limitations
weakref.proxy creates a proxy object that behaves like the original object but does not keep it alive. Accessing attributes or calling methods on the proxy forwards the operation to the underlying object if it exists. If the object has been collected, accessing the proxy raises ReferenceError.
import weakref class Counter: def increment(self): return 1 counter = Counter() proxy = weakref.proxy(counter) print(proxy.increment()) # 1 del counter # proxy.increment() # raises ReferenceError
Proxies are convenient because they avoid the explicit ref() call, but they have limitations. You cannot use a proxy as a dictionary key or as an argument to functions that expect the exact type, because the proxy is not the actual object. Also, proxy creation is slightly more expensive than a simple weakref.ref. In most cases, weakref.ref is the safer and more explicit choice.
Types That Do Not Support Weak References
Not every Python object can have a weak reference. Built-in types such as int, str, tuple, and list do not support weak references by default because they do not have a __weakref__ attribute. Attempting to create a weak reference to one raises TypeError.
import weakref try: ref = weakref.ref(42) except TypeError as e: print(e) # cannot create weak reference to 'int' object
You can add weak reference support to a custom class by including a __weakref__ slot, but for built-in types you must either subclass them or use a different design. For example, if you need weak references to integers, you could wrap them in a custom class that supports weak references.
This limitation also affects WeakValueDictionary and WeakKeyDictionary. You cannot store a plain integer as a value in a WeakValueDictionary because the dictionary attempts to create a weak reference to it. The same applies to using a tuple as a key in a WeakKeyDictionary.
Memory Overhead and When to Avoid Weak References
Weak references are not free. Each weak reference object consumes memory and requires extra bookkeeping by the garbage collector. Creating a weak reference is slower than creating a strong reference, and accessing the object through a weak reference involves an extra indirection. For most applications, the overhead is negligible, but in hot paths or when creating millions of weak references, it can add up.
More importantly, weak references can introduce subtle bugs. If you rely on a weak reference to keep an object alive, the object may disappear at an unexpected time, especially if the garbage collector runs during a critical section. This is why weak references are best used for non-essential data such as caches, where losing an entry is acceptable, rather than for critical program state.
A common mistake is to use a weak reference to store a value that you still need. For example, a WeakValueDictionary that stores the only reference to a value will lose that value immediately. The value must be strongly referenced elsewhere for the cache to be useful.
When you need deterministic cleanup, a regular dictionary with explicit removal is often simpler and more predictable. Weak references are a tool for specific scenarios, not a general replacement for strong references. Use them when you want to avoid preventing garbage collection, and measure the impact if you are unsure whether the overhead matters.