Skip to content

← Under the Hood: Objects, Memory, Speed step 9 of 35

Medium Primitives

A registry that does not prevent collection

Build an InstanceRegistry that can find live objects by key and does not keep them alive.

The starter is a plain dict. It typechecks, it is fast, and it is the reason the service’s RSS only goes up: a module-level registry of strong references is a leak that looks like a lookup table.

Reimplement it so that:

  • register(key, obj) records obj weakly.
  • get(key) returns the object if it is still alive, otherwise None.
  • __len__ counts only the entries whose object is still alive.
  • Registering an object that cannot be weak-referenced raises TypeError. You should not need to write that check — it is what weakref.ref already does, and the point of the test is that you let it through rather than swallowing it.

solve drives the scenario:

def solve(register: list[str], drop: list[str], probe_slotted: bool) -> dict[str, object]: ...

It registers one Resource per key in register, holding a strong reference to each in a local dict; checks that get(key) is that same object; drops the strong references named in drop; calls gc.collect(); and then reports:

{"live": [...], "size": int, "identity_ok": bool, "slotted_error": "" | "TypeError"}

Two things this is really teaching.

SlottedResource is a @dataclass(slots=True) — no __dict__, and no __weakref__ slot either, so weakref.ref on it raises TypeError. That is not a quirk; it is the memory optimisation and the weak-reference capability being the same underlying decision. @dataclass(slots=True, weakref_slot=True) is how you get both.

weakref.ref[T] called returns T | None. Under mypy --strict you cannot write self._refs[key]().name — the checker makes you name the already-collected branch, which is a state untyped code silently ignores until the day it happens under memory pressure.

Your submission must pass mypy --strict.