skip to content

What does weakref.ref give you that an ordinary Python reference does not?

level: juniorimportance: must knowfreq 40%

answer

  1. A pointer with no vote on lifetime
  2. Does not raise the count that keeps it alive
  3. Call it and you may get nothing
  4. Returns None once the referent dies
  5. Lists and ints refuse it outright

basics

~10 s

A weakref.ref points at an object without counting toward the references that keep it alive. Call the ref like a function to get the object back, or None once the object has been collected.

solid answer

~40 s

CPython frees an object when the last **strong** reference to it goes away. Names, list elements, dict values and attributes are all strong references; `weakref.ref(obj)` is not, so it never on its own keeps `obj` alive. A `weakref.ref` is callable: `r()` returns the referent, or `None` after it has been collected — so the safe idiom is to call it once, bind the result to a local name (that local *is* a strong reference, pinning the object while you use it), then test for `None`. `weakref.proxy` is the transparent variant: it forwards attribute access to the referent and raises `ReferenceError` when the referent is dead. Both accept an optional callback that fires on collection. Not every object supports this: instances of classes you define do, but `list`, `dict`, `tuple`, `int` and `str` do not.

code

python · 13 lines
python
import weakref


class Session:
    pass


s = Session()
r = weakref.ref(s)
print(r() is s)   # True

del s             # last strong reference gone
print(r())        # None

go deeper

for a junior

Be ready to say in one sentence that a weak reference does not keep its target alive, and to show calling weakref.ref and checking the result for None before using it.

for a middle

Explain the mechanics: strong references drive deallocation, weakref.ref stays out of that count, weakref.proxy forwards attributes and raises ReferenceError, and the __weakref__ slot decides which types are eligible at all.

for a senior

Show the call-once-then-bind-a-local discipline and be able to point at the callback-captures-the-referent bug. An interviewer expects you to name where weak references genuinely belong — registries, observers, back-pointers — and where they are the wrong tool.

for a principal

Own the design position: weak references make ownership explicit, so the question you push back on is who holds the object strongly. Be ready to argue when a weak link is a clean fix versus a smell hiding an unclear lifetime contract.

**A weak reference is a reference that does not get a vote on whether the object stays alive.** CPython destroys an object when the number of *strong* references to it reaches zero. Every ordinary name binding, every list element, every dict value, every attribute and every argument sitting on the call stack is a strong reference, and while even one of them exists the object cannot be reclaimed. `weakref.ref(obj)` builds a small separate object that points at `obj` **without** joining that population. When the last strong reference disappears, `obj` is destroyed exactly as if the weak reference had never been created, and the weak reference becomes *dead*. ### Reading through one A `weakref.ref` is callable with no arguments. It returns the referent, or `None` if the referent is gone: ```python import weakref r = weakref.ref(session) obj = r() if obj is None: ... # it is already gone else: obj.charge() ``` That `None` return is the entire safety story. There is deliberately no separate `is_alive()` you can consult and then act on, because between the check and the use the object could die. The correct idiom is the one above: call the ref **once**, bind the result to a local name — that local is itself a strong reference, so the object is now pinned for as long as the local lives — and only then test it against `None`. ### ref versus proxy `weakref.proxy(obj)` wraps the same machinery in a transparent object: attribute access, indexing and operators are forwarded to the referent, so most code cannot tell it is holding a proxy. When the referent is dead, using the proxy raises `ReferenceError` instead of quietly returning `None`. The tradeoff is that proxies are **not hashable**, so a proxy cannot be a dict key or a set member, and `is` comparisons against the real object fail. Prefer `weakref.ref` when you want the holder to be explicit about the weakness or you need hashing; prefer `weakref.proxy` when you are handing the object to code that should not know. ### Callbacks Both `weakref.ref` and `weakref.proxy` take an optional second argument: a callable invoked with the (now dead) weak reference once the referent is collected. The classic bug is a callback that closes over the referent — that closure is a strong reference, so the object never dies and the callback never runs. A callback must capture only the data it needs, never the object itself. `weakref.finalize` is the friendlier wrapper over the same mechanism. ### Not everything can be weakly referenced A type supports weak references only if its instances carry a `__weakref__` slot. Instances of classes written in Python get one for free. Many builtins do not: `weakref.ref([1, 2])` raises `TypeError: cannot create weak reference to 'list' object`, and the same applies to `dict`, `tuple`, `int`, `str`, `bytes` and a bare `object()`. Functions, classes, modules, `set` and `frozenset` *are* weak-referenceable. Two escape hatches exist: subclass the builtin (instances of `class Basket(list): pass` are weak-referenceable), or, if you use `__slots__`, add `'__weakref__'` to the slots tuple, because declaring `__slots__` otherwise removes the slot. ### Where it earns its keep Three shapes recur. **Registries and caches** that must not be the reason an object survives — a mapping from an id to a live object where the mapping is a convenience, not an owner. **Observer lists**, so subscribing to an event source does not immortalise the subscriber. **Back-pointers**: a child holding a strong reference to its parent while the parent holds its children creates a reference cycle that plain counting cannot free, and making the child-to-parent link weak breaks the cycle at the design level rather than leaving it to the collector. ### What it is not A weak reference does not make an object die *sooner*; the object dies when the last strong reference goes, and taking a weak one changes nothing about that moment. It is not a cache-eviction policy — it evicts on the whims of your program's ownership, not on size or age. And it is not a substitute for releasing external resources deterministically: a socket or a file should be closed by a `with` block, not left to the moment some referent happens to be collected. Weak references are about *object lifetime*, and the discipline they impose is that somebody, somewhere, must still own the object strongly — otherwise it evaporates the instant you look away.

  • Which common built-in types can you not create a weakref.ref to, and what is the workaround?
    `list`, `dict`, `tuple`, `int`, `str`, `bytes` and a bare `object()` all raise `TypeError` because their instances have no `__weakref__` slot. Functions, classes, modules, `set` and `frozenset` do support it, as do instances of classes defined in Python. The workarounds are to subclass the builtin, or — if the class declares `__slots__`, which removes the slot — to include `'__weakref__'` in the slots tuple.
  • What is the practical difference between weakref.ref and weakref.proxy?
    `weakref.ref` is an explicit handle: you call it and check for `None`, and it is hashable, so it can live in a set or be a dict key. `weakref.proxy` forwards attribute access to the referent so calling code cannot tell, and raises `ReferenceError` once the referent is dead. Proxies are unhashable and do not compare identical to the referent, so they are unsuitable as keys.
  • Does taking a weak reference change what sys.getrefcount reports for the object?
    No — that is precisely the point. `weakref.ref(obj)` leaves the strong count untouched, which is why the object can still be freed while the weak reference exists. The weak reference itself is a separate object with its own lifetime; when the referent dies, CPython clears the weak reference and, if one was registered, runs its callback.

It is a phone number for someone who never promised to stay: you can dial it any time, but the line may simply be disconnected, and holding the number never obliged them to keep the flat.

saying these in an interview costs you the question

  • Says a weak reference keeps the object alive until it is called
  • Expects a dead weakref.ref to raise rather than return None
  • Believes any object, including a list or an int, can be weakly referenced
  • Thinks weakref.ref makes a copy of the object
  • Claims taking a weak reference causes the object to be freed sooner
  • Uses a callback that closes over the referent it is watching

context