skip to content

When would you reach for weakref.WeakKeyDictionary rather than WeakValueDictionary?

level: middleimportance: nice to knowfreq 20%

answer

  1. Two mappings, opposite sides weakened
  2. Data about an object you do not own
  3. The class may use __slots__ or be third-party
  4. The annotation dies with its subject
  5. Values are strong — beware the back-reference

basics

~20 s

Use weakref.WeakKeyDictionary to attach data to an object you do not own: keys are weak, values strong, so the note dies with its subject. WeakValueDictionary is the mirror — find a live object by key.

solid answer

~50 s

They weaken opposite sides. `weakref.WeakValueDictionary` holds keys strongly and values weakly — "find this object by key while it lives". `weakref.WeakKeyDictionary` holds keys weakly and values strongly — "remember this data about the object for as long as it lives". The second is a side table: per-object state for a class you cannot modify, or one using `__slots__`, or state that belongs to your layer rather than the object's type. A plain `dict` keyed the same way would pin every annotated object forever; weak keys make the table's size track the live population with no eviction policy. The trap is that values are strong, so a value holding a back-reference to its own key keeps that key alive permanently. `weakref.WeakSet` is the payload-free version — a registry of live members that never keeps one alive.

code

python · 15 lines
python
import weakref


class Transcript:
    def __init__(self, key):
        self.key = key


meta = weakref.WeakKeyDictionary()
t = Transcript("room-7")
meta[t] = {"parsed_seconds": 45.0}
print(len(meta), meta[t])

del t
print(len(meta))

go deeper

for a junior

Recall that the two class names are literal: one weakens keys, the other weakens values. Know that a weak-key mapping is how you attach data to an object without keeping it alive.

for a middle

Explain when a side table beats an attribute — a third-party class, a class using slots, or bookkeeping that belongs to your layer — and know that the values are strong, so a back-reference to the key defeats the whole arrangement.

for a senior

Show the decision rule out loud: if the data must outlive the object, no weak container is right and you need a bounded ordinary mapping with a real eviction policy. Pair weak keys with weakref.finalize when the moment of death itself matters.

for a principal

Own the boundary question: whose bookkeeping is this, and should it be visible on the object's own type at all? Side tables keep foreign concerns out of a shared class, at the cost of state that is harder to inspect and easy to leak through a back-reference.

### Which side is weak? The two mappings are mirror images and the names say which. `weakref.WeakValueDictionary` holds its keys strongly and its values weakly: it answers "while this object lives, find it by key". `weakref.WeakKeyDictionary` holds its keys weakly and its values strongly: it answers "while this object lives, remember something *about* it". The second is what people call a side table — data attached to an object from the outside, without touching the object at all. ### Why you would want a side table You reach for `WeakKeyDictionary` when you need per-object state and cannot, or should not, store it on the object: * the class is not yours to modify — it comes from a library, or it is a built-in subclass; * the class declares `__slots__`, so you cannot add an attribute at all; * the state is a concern of *your* layer (a parse timestamp, a lock, a computed digest) and putting it on the object would leak your bookkeeping into someone else's type; * several independent subsystems each want their own annotation on the same objects and must not collide in one namespace. The decisive property is lifetime. A plain `dict` keyed by those objects would keep every one of them alive forever — the classic "my annotation table is the leak" bug. With weak keys, the entry evaporates the moment the annotated object is collected, so the table's size tracks the population of live objects automatically and needs no eviction policy of its own. `weakref.WeakSet` is the same idea with no payload: a registry of live members — observers, listeners, open sessions — that never stops any of them from being collected. Iterating it gives you exactly the ones still alive. ### The leak that survives weak keys The one trap worth rehearsing: the *values* of a `WeakKeyDictionary` are strong, so if a value refers back to its own key, the key can never die. The mapping holds the value strongly, the value holds the key strongly, the key therefore has a live strong reference, and the weak key never clears. This is easy to write by accident — storing a small record object that carries a `self.owner` back-pointer, or a closure that captured the key. The fix is either to store only data that does not point back, to store a `weakref.ref` to the key inside the value, or to key on a cheap identifier instead. ### Constraints on the keys A `WeakKeyDictionary` key must satisfy *both* contracts: hashable and weakly referenceable. That excludes `str`, `tuple` and `int` — hashable but not weakly referenceable — and it excludes any unhashable object regardless. In practice keys are user-defined instances, which are hashable by identity by default and weakly referenceable by default, so the two constraints line up. Note that lookup still goes through the key type's `__hash__` and `__eq__`; a `WeakKeyDictionary` is not an identity map unless the key class leaves the default identity-based hashing in place. A class that defines value equality will have two distinct-but-equal keys share one entry, and whichever object dies first can take the entry with it. ### Choosing between the three Ask what you have and what you need: * You have a key and want to find an object that something else keeps alive → `WeakValueDictionary`. * You have an object and want to remember something about it for as long as it lives → `WeakKeyDictionary`. * You have objects and only need to know which of them are still alive → `WeakSet`. * You need the data to *outlive* the object → none of these; use a plain `dict` and take responsibility for eviction. That last line is the one that separates a considered answer from pattern-matching. Weak containers are not "the memory-safe dict"; they are a way of saying "this mapping is a follower, not an owner". If the mapping is genuinely the owner of the data — an audit log, a result you must return later, a counter you will report at shutdown — weakness is the wrong tool and a bounded ordinary container is right. ### Cleanup hooks When you need to *act* at the moment an annotated object goes away rather than merely forget it, pair the mapping with `weakref.finalize(obj, callback, *args)`. It registers a callable to run when the object is collected, it does not resurrect the object, and it is safer than writing `__del__` because it never joins the object into a cycle and its arguments are held explicitly. Take care that the callback's arguments do not include the object itself — that would keep it alive and the finalizer would never fire, which is the same back-reference mistake as above wearing a different hat.

  • How can a WeakKeyDictionary still leak despite holding its keys weakly?
    Its values are strong. If a stored value refers back to its own key — a record with an owner attribute, or a closure that captured the key — the mapping holds the value, the value holds the key, and the key's reference count never reaches zero, so the weak key never clears. Store data that does not point back, keep a `weakref.ref` to the key inside the value, or key on a cheap identifier instead.
  • What must a key satisfy to go into a weakref.WeakKeyDictionary?
    Both hashable and weakly referenceable. That combination excludes `str`, `tuple` and `int`, which are hashable but have no weak-reference slot, and it excludes anything unhashable. In practice keys are user-defined instances, which are hashable by identity and weakly referenceable by default. Lookup still uses the key type's `__hash__` and `__eq__`, so a class defining value equality will make two distinct objects share one entry.
  • You need to run cleanup at the moment an annotated object is collected. What do you use?
    `weakref.finalize(obj, callback, *args)`, registered when you insert the annotation. It runs the callable once the object is collected, does not resurrect it, and avoids the pitfalls of writing `__del__`. Critically, none of the arguments may be the object itself — that would be a strong reference keeping it alive, so the finalizer would never fire, which is the same back-reference mistake in a different place.

A weak-key side table is a sticky note on someone else's file: it says something about the file, it is thrown away when the file is, and the filing cabinet never keeps a file around just because a note was stuck to it.

saying these in an interview costs you the question

  • Cannot say which side each weak mapping weakens
  • Thinks WeakKeyDictionary values are held weakly too
  • Stores a value that points back at its own key
  • Uses a weak mapping for data that must outlive the object
  • Assumes a string or tuple can be a weak key

context