skip to content

Finalization and Weak References

__del__ is a finalizer, not a destructor: it runs when the interpreter decides, swallows exceptions and can resurrect the object. weakref hands you a reference that does not keep it alive.

part ofPythonoverview, primer and where to startread it →
on this pageshow

questions

4

What is the difference between `weakref.ref` and `weakref.proxy` in Python?

level: middleimportance: must knowfreq 38%

answer

  1. Neither keeps the object alive
  2. One is a callable, one impersonates
  3. The dead cases differ sharply
  4. None versus ReferenceError
  5. Proxies are never hashable

basics

~20 s

Both refer to an object without keeping it alive. A weakref.ref is a callable: call it to get the object, or None once it is gone. A weakref.proxy forwards attribute access through, and raises ReferenceError once it is gone.

solid answer

~50 s

A **weak reference** points at an object without contributing to its reference count, so the object can be collected while the weak reference still exists. `weakref.ref(obj)` gives you a callable: `r()` returns the object, or `None` after it dies, which forces you to check before use and makes the dead case explicit. `weakref.proxy(obj)` gives you a stand-in that forwards attribute access, item access and most operators to the referent, so it reads like the object itself -- but any use after the referent dies raises `ReferenceError`, and proxies are deliberately unhashable, so they cannot be dict keys or set members. Use `ref` when you want to test liveness and branch; use `proxy` when you want a drop-in that must never silently outlive its target. Both accept an optional callback invoked when the referent is collected.

code

python · 19 lines
python
import weakref


class Node:
    def __init__(self, label):
        self.label = label


n = Node("root")
r = weakref.ref(n)
p = weakref.proxy(n)
print(r().label, p.label)

del n
print(r())                    # None: the referent is gone
try:
    p.label
except ReferenceError:
    print("proxy raised ReferenceError")

go deeper

for a junior

Know that a weak reference lets you point at an object without keeping it alive, that weakref.ref(obj) must be called with r() to get the object back, and that it returns None once the object is gone.

for a middle

Contrast the two dead-object behaviours precisely -- None from a called ref, ReferenceError from a proxy -- and be ready for the TypeError cases: list, dict, tuple, int, str and __slots__ classes without '__weakref__'.

for a senior

Demonstrate the design use: weak back-pointers to break parent-child cycles so structures free promptly under reference counting, and callbacks to deregister entries. Explain when a proxy's loud failure beats a ref's silent None.

for a principal

Argue about where weakness belongs in an architecture. Lifetime that is implicit in a reference graph is hard to reason about at scale, so treat weak references as a targeted tool for caches and back-pointers, not a general answer to memory growth.

### What a weak reference is CPython keeps an object alive as long as something refers to it. A **weak reference** deliberately opts out of that: it lets you name an object without owning it, so the object's lifetime is decided entirely by its other, strong references. The moment those go away, the object is collected and the weak reference goes dead. This is how you build caches, registries, back-pointers and observer lists that do not, by their mere existence, prevent anything from ever being freed. ### `weakref.ref` `r = weakref.ref(obj)` returns a weak reference **object**. It is not the referent and does not pretend to be. To reach the referent you call it: `r()` returns the object if it is still alive and `None` if it is not. The API is blunt on purpose -- the dead case is a value you must handle, which is exactly what you want when the whole point of the reference is that the object may vanish. A `weakref.ref` is hashable when its referent is (and keeps hashing consistently after the referent dies, provided it was hashed while alive), and two weak references compare equal if their live referents compare equal. That makes `ref` objects usable as dict keys, which is the machinery under `weakref.WeakKeyDictionary`. `weakref.ref` also takes an optional second argument, a callback receiving the weak reference itself, called when the referent is about to be finalized -- useful for removing the entry from a registry. ### `weakref.proxy` `p = weakref.proxy(obj)` returns a proxy that forwards operations to the referent. `p.attr`, `p[key]`, `p + other`, `len(p)` all reach through to the real object, so a proxy can be passed to code that has no idea weak references are involved. When the referent dies, every operation on the proxy raises `ReferenceError` instead of returning `None`, so a stale proxy fails loudly at the point of use rather than producing a confusing `AttributeError: 'NoneType' object has no attribute ...` somewhere else. Proxies have important limits. They are **not hashable**, regardless of the referent, because the referent's hash could become unavailable mid-life; that rules out using a proxy as a dict key or set member. `type(p)` is a proxy type, not the referent's class, and `isinstance` checks against the referent's class fail, so a proxy is not a perfect substitute. And a proxy of a proxy is not something to build on. ### What can be weakly referenced This is the part people get wrong in interviews. Not every object supports weak references: the type must have a slot for the list of weak references to it, exposed as `__weakref__`. Instances of classes defined in Python get one for free. Many built-in types do not: `list`, `dict`, `tuple`, `int`, `str` and bare `object()` instances all raise `TypeError: cannot create weak reference to ...`. `set` instances and functions, classes, methods and modules do support it. `list` and `dict` gain support if you subclass them, because your Python-level subclass adds the slot; `tuple` and `int` do not, even when subclassed. The other common cause of a surprising `TypeError` is `__slots__`. A class that defines `__slots__` and does not include `'__weakref__'` in it has no such slot, and its instances cannot be weakly referenced -- which in turn means they cannot be stored in a `weakref.WeakValueDictionary` or used with `weakref.finalize`. Adding `'__weakref__'` to the slots tuple restores the ability at the cost of one pointer per instance. ### Choosing between them, and what else lives here Use `weakref.ref` in code you own, where an explicit "is it still there?" check is honest and readable, and where you need hashability or a callback. Use `weakref.proxy` when you are handing the reference to code that expects the plain object and you want the dead case to be an immediate, obvious error. In practice much real code uses neither directly: `weakref.WeakValueDictionary`, `weakref.WeakKeyDictionary` and `weakref.WeakSet` wrap the mechanism into containers, and `weakref.finalize` wraps it into a cleanup registration. `weakref.getweakrefcount(obj)` tells you how many weak references currently point at an object, which is a handy debugging aid when a registry is not clearing itself. The canonical design use is breaking ownership cycles without relying on the cyclic collector: a parent holds strong references to its children, and each child holds a weak reference back to its parent. Dropping the parent then frees the whole structure promptly, and a child that outlives its parent discovers it cleanly rather than pinning a dead tree in memory.

  • Why does `weakref.ref([])` raise `TypeError`?
    A type can be weakly referenced only if it has a slot for its weak-reference list, surfaced as `__weakref__`. Built-in `list` does not carry one, and neither do `dict`, `tuple`, `int`, `str` or bare `object()` instances. Subclassing `list` or `dict` in Python adds the slot, so a subclass instance works; subclassing `tuple` or `int` does not help.
  • How do you use weak references to break a parent-child ownership cycle?
    Give the parent strong references to its children and give each child a weak reference back to the parent. No cycle exists, so dropping the parent frees the whole structure by reference counting alone, without waiting for the cyclic collector. Children reach the parent by calling the weak reference and handle the `None` case for an orphaned child.
  • What does the optional callback argument to `weakref.ref` do?
    `weakref.ref(obj, callback)` calls `callback(weakref_object)` when the referent is about to be finalized, passing the weak reference rather than the dead object. It is the hook used to deregister entries from a registry or index. The callback must not assume the referent is reachable, and an exception inside it is unraisable, exactly as in a finalizer.

A ref is a phone number you have to dial and may find disconnected; a proxy is a receptionist who patches you straight through, or tells you flatly that the person no longer works here.

saying these in an interview costs you the question

  • Says a weak reference just decrements the refcount
  • Thinks `weakref.ref(obj)` can be used like the object
  • Expects a dead proxy to return `None`
  • Assumes any object can be weakly referenced
  • Uses a `weakref.proxy` as a dictionary key
  • Confuses a weak reference with a shallow copy

context

open as a page

When does a `weakref.finalize` callback actually run in CPython?

level: juniorimportance: should knowfreq 22%

basics

~10 s

A weakref.finalize callback fires as soon as the object it was registered against is garbage collected, or at interpreter exit if that object is still alive. It runs at most once, whichever comes first.

open as a page

What happens to an exception raised inside `__del__` in Python?

level: middleimportance: should knowfreq 32%

basics

~20 s

It never propagates. There is no meaningful call site to raise it into, so CPython reports it through sys.unraisablehook, which by default prints a message starting "Exception ignored in" to standard error, and execution continues as if nothing happened.

open as a page

Why does a `weakref.WeakValueDictionary` cache of parsed log schemas keep emptying itself?

level: seniorimportance: should knowfreq 28%

basics

~20 s

Because its values are held only weakly. An entry survives exactly as long as something else keeps a strong reference to the value, so a freshly parsed schema that nothing else holds is collected before the next lookup.

open as a page