skip to content

Which objects can weakref.ref() point at, and which raise TypeError?

level: middleimportance: must knowfreq 38%

answer

  1. Support is a property of the type
  2. Somebody has to pay a pointer per object
  3. Small built-ins were left out on purpose
  4. __slots__ takes the slot away too
  5. Subclassing a tuple gets it back

basics

~20 s

An object supports weak references only if its type reserves the weakref slot. Instances of user-defined classes, functions, classes, modules, sets and generators do; int, str, bytes, tuple, list, dict and a bare object() do not, so weakref.ref() raises TypeError.

solid answer

~40 s

Weak-reference support is a property of the *type*, not the instance: the type must reserve a pointer for the object's weak-reference list, exposed as `__weakref__`. CPython omits that slot from many small fixed-layout built-ins to save a pointer each, so `weakref.ref(1)`, `weakref.ref('x')`, `weakref.ref(())`, `weakref.ref([])`, `weakref.ref({})` and `weakref.ref(object())` all raise `TypeError`. Instances of ordinary user-defined classes support it by default, as do functions, classes, modules, `set`, `frozenset`, `collections.deque` and generator objects — and so do subclasses of the refusing built-ins, because subclassing adds the slot. Two traps: a class declaring `__slots__` loses the slot unless `'__weakref__'` is listed, and a plain weak reference to `obj.method` dies instantly because the bound-method object is created fresh on each attribute access — that is what `weakref.WeakMethod` is for.

code

python · 25 lines
python
import weakref


class Session:
    __slots__ = ("room",)


class WeakSession:
    __slots__ = ("room", "__weakref__")


try:
    weakref.ref(Session())
except TypeError as exc:
    print("no slot:", exc)

alive = WeakSession()
print("with slot:", weakref.ref(alive)() is alive)

for value in (1, "room-7", (1, 2), [1], {}):
    try:
        weakref.ref(value)
        print(type(value).__name__, "weakly referenceable")
    except TypeError:
        print(type(value).__name__, "NOT weakly referenceable")

go deeper

for a junior

Recall that not everything can be weakly referenced and that the failure is a loud TypeError at the point you take the reference. Remember at least that ints, strings and tuples refuse it while your own class instances accept it.

for a middle

Explain the mechanism: the type must reserve the weakref slot, CPython omits it from small built-ins to save a pointer per object, subclassing adds it back, and slots removes it unless you list it. Name the bound-method trap and weakref.WeakMethod.

for a senior

Show that you check rather than recall — one line in a REPL settles it — and that you design around the constraint: weak keying tracks object lifetime, so value-like keys go in a bounded ordinary mapping instead of a weak one.

for a principal

Frame it as a deliberate space tradeoff CPython makes on behalf of every program, and as an API constraint your own base classes impose downstream: adding slots to a widely used class quietly removes a capability from every consumer of it.

### The rule: the type must reserve a slot Weak references are not free. For an object to be weakly referenceable, its type must reserve room for a pointer to the object's list of weak references — the slot exposed as `__weakref__`. CPython deliberately omits that slot from many small, fixed-layout built-in types, because an extra pointer per object would be a real cost on the millions of integers, strings and tuples a program creates. When the slot is absent, `weakref.ref(obj)` raises `TypeError: cannot create weak reference to 'X' object`. There is no workaround at the instance level: it is a property of the type. ### What works and what does not Weakly referenceable, and the ones worth remembering: * instances of ordinary user-defined classes — the default, and the reason weak references feel ubiquitous in application code; * functions, bound methods, classes and modules; * `set`, `frozenset`, `collections.deque`, `array.array`, generator objects, file objects; * subclasses of the built-ins below — subclassing adds the slot, so `class Key(tuple): pass` produces instances you *can* weakly reference even though `tuple` itself refuses. Not weakly referenceable: * `int`, `float`, `complex`, `bool`, `str`, `bytes`, `bytearray`, `tuple`, `list`, `dict`, `range`, `slice`; * a bare `object()` — surprisingly often the thing someone reaches for as a sentinel; * instances of `types.SimpleNamespace` and exception instances such as `ValueError('x')`; * instances of a class that declares `__slots__` without listing `'__weakref__'`. ### The `__slots__` interaction `__slots__` replaces the per-instance `__dict__` with a fixed set of descriptors, and in doing so it also drops the weak-reference slot unless you ask for it back. So a memory-optimised class silently becomes unusable as a `weakref.WeakValueDictionary` value or a `weakref.WeakKeyDictionary` key. The fix is one string: ```python class Session: __slots__ = ("room", "__weakref__") ``` This is the failure people hit months after adopting `__slots__`, when someone else tries to put the class into a weak mapping and gets a `TypeError` from a line that looks perfectly ordinary. ### Bound methods: referenceable but useless `weakref.ref(obj.method)` succeeds — a bound method object supports weak references — and then returns `None` immediately. The reason is that `obj.method` is not a stored attribute: the attribute access *creates* a new bound-method object each time, wrapping the function and the instance. As soon as the expression finishes, that temporary object has no strong reference and is collected, leaving your weak reference dead on arrival. This burns people writing callback registries that must not keep listeners alive. `weakref.WeakMethod` exists for exactly this: it holds weak references to the underlying function and instance separately and rebuilds the bound method on call, returning `None` only once the instance itself is gone. ### Why this matters beyond `weakref.ref` Every weak container inherits the constraint. A `WeakValueDictionary` refuses to store a string, a tuple or an int as a value; a `WeakKeyDictionary` requires keys that are *both* hashable and weakly referenceable — which rules out the very built-ins that are most obviously hashable, like `str` and `tuple`. A `WeakSet` requires the same of its members. Newcomers often try to build a weak memoisation table keyed by argument tuples and discover the design cannot exist; the answer is that weak keying is for *object identity lifetimes*, not for value-like data, and value-like data should go in a bounded ordinary mapping instead. ### How to check, and how to talk about it The honest interview answer is the rule plus two or three concrete examples, not a memorised list. Say: "an object is weakly referenceable if its type reserves the weak-reference slot; user-defined classes do by default, most small built-ins deliberately do not, and `__slots__` removes it unless you list `'__weakref__'`." Then demonstrate that you would confirm rather than guess — `weakref.ref(x)` in a REPL, or `weakref.getweakrefcount(x)`, tells you in one line. Interviewers ask this because the failure is a `TypeError` at an unexpected place, and because knowing *why* the slot is missing shows you understand that CPython trades a pointer per object against a feature most objects never use. One last subtlety: support for weak references says nothing about *when* the referent dies. A weakly referenceable object trapped in a reference cycle keeps its reference count above zero until the cycle collector runs, so the weak reference clears late rather than never. Type support and object lifetime are two independent questions, and mixing them up is a common way to misdiagnose a weak mapping that looks like it is holding on.

  • Why does weakref.ref(obj.method) return None immediately even though it did not raise?
    `obj.method` is not stored anywhere — the attribute access builds a new bound-method object wrapping the function and the instance, and that temporary is discarded as soon as the expression ends. The weak reference outlives its referent by a fraction of a statement. `weakref.WeakMethod` solves it by weakly referencing the function and the instance separately and reconstructing the bound method when called, clearing only when the instance dies.
  • How would you make a class that uses __slots__ usable as a WeakValueDictionary value?
    Add `'__weakref__'` to the `__slots__` tuple. `__slots__` replaces the instance `__dict__` with fixed descriptors and drops the weak-reference slot at the same time, so the class must ask for it back explicitly. It costs one pointer per instance — the same pointer the ordinary layout would have carried — and restores support for weak references, weak mappings and `weakref.finalize`.
  • You need a weak mapping keyed by a tuple of arguments. What do you do instead?
    Nothing weak: `tuple` is hashable but not weakly referenceable, so `weakref.WeakKeyDictionary` cannot key on one, and subclassing `tuple` only to gain the slot buys an object lifetime that nothing meaningful controls. Weak keying tracks the lifetime of an *object*, not the identity of a value, so value-like keys belong in an ordinary bounded mapping with an explicit eviction policy.

Weak-reference support is like a mailbox slot cut into a house at build time: you cannot add one to a finished house, and the builder left it out of the cheapest models on purpose.

saying these in an interview costs you the question

  • Says every Python object can be weakly referenced
  • Thinks weak-reference support is per instance, not per type
  • Forgets that __slots__ removes the weak-reference slot
  • Believes weakref.ref(obj.method) keeps a usable callback
  • Confuses being hashable with being weakly referenceable
  • Claims a tuple subclass still refuses weak references

context