pyguides

Weak References and the weakref Module

The weakref module gives you a non-owning handle to an object: the handle does not bump the object’s reference count, so it never keeps the object alive. Once the last strong reference is gone, the object is reclaimed and the weak handle becomes a tombstone. Most code never needs this, but caches, observer registries, and any place you would otherwise create a reference cycle end up using it.

Key Takeaways

  • weakref.ref(obj) returns a callable that yields the referent while it is alive and None after it dies. The liveness check has to be atomic with use, so prefer if (obj := r()) is not None: over a separate “is it alive” probe.
  • weakref.proxy(obj) forwards attribute access to the referent and raises ReferenceError after it dies. Proxies are never hashable, even when the referent is.
  • WeakValueDictionary, WeakKeyDictionary, and WeakSet are the container-shaped variants. Pick by which side you want held weakly.
  • WeakMethod is required for bound methods, since a plain weakref.ref(c.method) is dead on arrival. weakref.finalize is the modern way to register a one-shot cleanup that survives losing your reference to the finalizer.
  • int, tuple, and many interned strings cannot be weakly referenced, and any class that defines __slots__ has to include the string '__weakref__' to opt back in.

Every signature and behavior note below is verified against the Python 3.14 weakref docs.

Why weak references exist

CPython frees memory by reference counting first, with a cyclic GC to clean up the rest. Strong references feed into that count; weak references do not. That single difference is the whole module: it lets you remember an object without owning it. The docs group the practical use cases into four buckets: caches and mappings of large objects, breaking reference cycles, observer and notification systems, and registering a finalizer to run when an object dies.

How do you create a weak reference with weakref.ref?

weakref.ref(object, callback=None) returns a callable weak reference. Calling it returns the referent while it lives and None after it dies:

import weakref

class Object:
    pass

o = Object()
r = weakref.ref(o)
assert r() is o
del o
assert r() is None

Liveness is racy in any program with more than one thread. The docs spell out the safe idiom: bind the referent and check it in a single expression, using the walrus operator:

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

A separate “is it alive” check loses its truth value the instant the calling thread yields, so the walrus form is the only one that is safe to write in a multi-threaded program. weakref.getweakrefcount() and weakref.getweakrefs() are the diagnostic counterparts; both count or list every weak reference and proxy that points at an object, including entries that have been finalized but not yet swept.

How do callbacks work and when do they fire?

The optional callback argument to weakref.ref() is invoked when the referent is about to be finalized, with the weakref itself as its sole argument. By the time the callback runs, the referent is already gone, so close over any state you need before the reference dies. Callbacks run in LIFO order, exceptions inside them are written to stderr and not propagated, and the read-only __callback__ attribute (new in 3.4) exposes whichever callback was registered.

import sys
import weakref

def cb(name):
    print(f"finalizing: {name}", file=sys.stderr)

class Thing:
    pass

t = Thing()
weakref.ref(t, lambda r: cb("first"))
weakref.ref(t, lambda r: cb("second"))   # registered later, fires first
del t
# output (to stderr):
# finalizing: second
# finalizing: first

If you need to do anything more than a one-liner at object death, switch to weakref.finalize (covered below). It owns its own strong reference until the referent dies and survives even if you lose the finalizer object, so it sidesteps the “I dropped the only ref to my cleanup” bug that plain callbacks fall into.

What does weakref.proxy add on top of ref?

weakref.proxy(object, callback=None) returns a transparent forwarder: every attribute access goes to the referent. Accessing an attribute on a dead proxy raises ReferenceError, the same way a dead ref() call returns None. If object is callable, the result is a CallableProxyType; otherwise it is a ProxyType. The weakref.ProxyTypes tuple is a handy way to test for either:

import weakref

class Greeter:
    def __init__(self, name):
        self.name = name

g = Greeter("Ada")
p = weakref.proxy(g)
p.name              # 'Ada' — forwarded
isinstance(p, weakref.ProxyTypes)   # True
del g
p.name              # ReferenceError

Two things to know. Proxies are never hashable, even when the referent is, so you cannot use one as a dict key or as a member of a set. And since 3.8, proxies support the @ and @= matrix-multiplication operators, so a class that defines __matmul__ can hand out a proxy without breaking that syntax.

How do the three weak containers work?

WeakValueDictionary holds its values weakly, which makes it the right shape for a cache of large objects keyed by something cheap like id(). The docs ship this exact pattern:

import weakref

_id2obj_dict = weakref.WeakValueDictionary()

def remember(obj):
    oid = id(obj)
    _id2obj_dict[oid] = obj
    return oid

def id2obj(oid):
    return _id2obj_dict[oid]

Drop the strong reference and the entry vanishes the next time you look it up. No manual eviction, no LRU bookkeeping. The valuerefs() method (added in 3.9 alongside the | and |= operators from PEP 584) returns an iterable of weak references to the values, but those references are not guaranteed to still be live when you iterate them, so check each one before use.

WeakKeyDictionary holds its keys weakly, which is the right shape when you want to attach metadata to objects you do not own. The keyrefs() method has the same liveness caveat as valuerefs(). There is a footgun the docs warn about: assigning a new key that is equal but not identical to an existing key replaces the value but keeps the original key object in the dict. The workaround is to del d[old_key] before reassigning.

WeakSet is the same idea for a set. Members are dropped as their last strong reference dies. Iteration is fine, but elements still have to be hashable and weak-referenceable, and the set itself cannot be used as a dict key or as a member of another set.

Why is WeakMethod required for bound methods?

A bound method is rebuilt on every attribute lookup, so a plain weakref.ref to one is dead before you can use it:

import gc
import weakref

class C:
    def hello(self):
        return "hi"

c = C()
weakref.ref(c.hello)()      # None — dead on arrival
wm = weakref.WeakMethod(c.hello)
assert wm()() == "hi"       # works
del c
gc.collect()
assert wm() is None

WeakMethod (added in 3.4) re-binds the original function to the instance on each call, so it stays alive until either the instance or the function goes away. Reach for it whenever you need to hold a callback to a method without keeping the owning instance alive.

When should you use weakref.finalize?

weakref.finalize(obj, func, /, *args, **kwargs) registers func(*args, **kwargs) to run when obj is collected. The finalizer owns its own strong reference to obj until then, which is the safety net: you can construct the finalizer, ignore the return value, and the cleanup still runs. Without that, the classic “I lost the only reference to my cleanup callback, so cleanup never ran” bug is easy to write.

import weakref

class Resource:
    def __init__(self, name):
        self.name = name
        weakref.finalize(self, print, f"cleaned up {name}")

r = Resource("socket-1")    # no reference to the finalizer is kept anywhere
del r
# output: cleaned up socket-1

A few rules. func, args, and kwargs must not hold a reference to obj, directly or indirectly, or the cycle is permanent and obj is never collected. In particular, do not pass a bound method of obj as func; close over the data you need and pass a plain function instead. Finalizers whose atexit flag is True (the default) still run at interpreter shutdown, in reverse order of creation, but only if they reach that point alive. Each finalizer’s callback runs at most once, exceptions inside it are written to stderr, and you can also call __call__() on a live finalizer to run it early, query alive, peek at the registration via peek(), or kill it with detach().

Which objects can and can’t be weakly referenced?

The docs list class instances, Python functions, instance methods, sets, generators, type objects, sockets, deques, and compiled regex patterns as directly supported. list and dict are not, but a subclass works. int and tuple cannot be weakly referenced even when subclassed; the docs label this a CPython implementation detail, and PyPy may differ.

Anything that defines __slots__ loses weak-reference support unless the string '__weakref__' is in the slots list:

class Point:
    __slots__ = ('x', 'y', '__weakref__')   # '__weakref__' is required
    def __init__(self, x, y):
        self.x, self.y = x, y

p = Point(1, 2)
weakref.ref(p)      # works because '__weakref__' is in __slots__

If you skip '__weakref__', you get TypeError: cannot create weak reference to 'Point' object the first time someone tries to hold a weak handle to a Point. The slots guide walks through the rest of the slots semantics.

Common mistakes

  • A separate “is this weakref alive” check is a race. Use the walrus form: if (obj := r()) is not None:.
  • weakref.ref(c.method) always returns a dead reference. Use weakref.WeakMethod for bound methods.
  • Passing a bound method of the object to weakref.finalize creates the very cycle the module is supposed to break. Pass a plain function and close over the data you need.
  • Assigning a new equal-but-not-identical key into a WeakKeyDictionary replaces the value but keeps the original key object in the dict. del d[old_key] first.
  • Holding a strong reference to a WeakValueDictionary value somewhere else defeats the cache. The whole point is that nothing else owns it.
  • Assuming a callback argument is the referent. By the time the callback runs, the referent is gone; the callback receives the weakref itself, so close over any state you need to see.

Conclusion

The right primitive depends on what you are trying to do. A cache of large objects keyed by id is a WeakValueDictionary. A registry that wants to track who owns what without owning anything is a WeakKeyDictionary or a WeakSet. A callback to a method is a WeakMethod. A “do this when the object dies” hook is weakref.finalize. Pick the most specific one and you will not have to think about cycles, leaks, or cleanup ordering.

If the slot gotcha caught your attention, the slots guide covers the rest of the memory model around it, and the gc module reference is the right place to read about the cyclic collector that interacts with weakref callbacks.

See Also