skip to content

What is the difference between weakref.WeakValueDictionary and WeakKeyDictionary?

level: middleimportance: should knowfreq 34%

answer

  1. The name says which half is weak
  2. One is an index, the other a side table
  3. An entry leaves when its weak half dies
  4. Storing a fresh object can leave the mapping empty
  5. The strong half can resurrect the leak

basics

~10 s

WeakValueDictionary holds its values weakly, so an entry disappears when the last strong reference to that value goes. WeakKeyDictionary holds its keys weakly, so an entry disappears when the key object dies.

solid answer

~40 s

Both are mappings that drop entries automatically, and the name tells you which half is weak. `weakref.WeakValueDictionary` is the identity cache: you map an id to a live object, and the moment nothing else in the process holds that object the entry vanishes, so the cache never becomes the reason a value survives. `weakref.WeakKeyDictionary` is the side table: you attach data to objects you do not own — a flag, a timestamp, a lock — and the entry disappears with the key, which also means the key must be hashable and weak-referenceable. `weakref.WeakSet` is the same idea for membership, typically an observer or listener registry. The trap in both is that the *other* half is still strong: a WeakKeyDictionary whose value refers back to its key keeps that key alive forever.

code

python · 17 lines
python
import weakref


class Invoice:
    def __init__(self, number):
        self.number = number


cache = weakref.WeakValueDictionary()
cache["inv-1"] = Invoice(1)   # temporary dies immediately
print(len(cache))             # 0

keep = Invoice(2)
cache["inv-2"] = keep
print(len(cache))             # 1
del keep
print(len(cache))             # 0

go deeper

for a junior

Learn the naming rule first: the weak half is the one in the class name, and the entry leaves when that half is collected. Being able to say which one suits a cache is enough at this level.

for a middle

Explain both directions with an example each — an identity cache for weak values, a side table of per-object metadata for weak keys — and state the eligibility rules that keys must be hashable and weak-referenceable.

for a senior

Demonstrate the failure analysis: name the half that is still strong, show how a value that references its own key makes a WeakKeyDictionary entry permanent, and mention the snapshot-before-iterating rule in a threaded process.

for a principal

Frame the choice as an ownership decision rather than a container choice. Be ready to argue when a weak mapping is the right answer versus when the honest fix is an explicit lifecycle with deliberate removal.

Both classes live in `weakref` and both are ordinary mutable mappings you can index, iterate and `len()`. What differs is which side of the entry is a weak reference, and therefore which object's death removes the entry. ### WeakValueDictionary — the mapping does not own the values `weakref.WeakValueDictionary` stores its **values** weakly. Keys are held normally. An entry survives only while somebody else in the process holds a strong reference to the value; when that last holder lets go, the entry is removed automatically. ```python import weakref cache = weakref.WeakValueDictionary() cache['inv-1'] = Invoice(1) # nothing else holds it print(len(cache)) # 0 - already gone keep = Invoice(2) cache['inv-2'] = keep print(len(cache)) # 1 del keep print(len(cache)) # 0 ``` That first line is the whole personality of the class in one statement, and it surprises people constantly. The canonical use is an **identity map**: while any part of the program is working with invoice 42, everyone asking for invoice 42 gets the *same* object; as soon as nobody is working with it, the mapping forgets it. The mapping is an index, never an owner. ### WeakKeyDictionary — the mapping does not own the keys `weakref.WeakKeyDictionary` stores its **keys** weakly and its values strongly. The entry lives as long as the key object lives. This is the *side table*: you want to associate data with objects you did not write and cannot add an attribute to — how many times a connection has been retried, when a customer record was last validated, a per-object lock. A plain `dict` used this way is a permanent leak, because the dict's own reference to the key keeps every object it has ever seen alive for the life of the process. ```python import weakref audit = weakref.WeakKeyDictionary() customer = Customer(7) audit[customer] = 'validated' print(len(audit)) # 1 del customer print(len(audit)) # 0 ``` Two requirements follow from the design. Keys must be **hashable**, like any dict key, and they must be **weak-referenceable**, which rules out `str`, `int`, `tuple` and other builtins whose instances have no `__weakref__` slot. In practice this means the keys are objects, not values — which is exactly right, because a side table keyed by identity is what you wanted. ### WeakSet `weakref.WeakSet` is membership with the same rule: an element is in the set only while something else holds it. It is the natural container for observers and listeners, so that registering for an event does not make the subscriber immortal — a leak that otherwise builds silently every time a short-lived object subscribes and forgets to unsubscribe. `weakref.WeakMethod` completes the picture for bound methods, which would otherwise die immediately because the bound-method object is created fresh on each attribute access. ### The trap: the other half is still strong The most common defect with either class is a strong reference smuggled in through the half you did not make weak. * In a `WeakKeyDictionary`, the **value** is strong. If the value refers back to its key — a wrapper, a closure, a record holding the object — then the mapping keeps the value alive, the value keeps the key alive, and the key never dies. Nothing is ever removed. The entry is self-sustaining and you have rebuilt the leak you were avoiding. * In a `WeakValueDictionary`, the **key** is strong. Values evaporating is the intended behaviour, but the keys accumulate until their entries are removed, so a mapping churned with millions of distinct keys is still doing real work per key. ### Choosing between them Ask who should own the object. If the object's real owner is elsewhere and this mapping is merely an index for looking it up, the values are weak — `WeakValueDictionary`. If the object's real owner is elsewhere and this mapping is annotating it, the keys are weak — `WeakKeyDictionary`. If your mapping is the *only* thing that should keep the object alive, neither is appropriate: use a plain `dict` and decide explicitly when entries leave it. Finally, mutation during iteration is riskier here than with a plain `dict`: entries can vanish between one step of a loop and the next because a collection happened elsewhere, in another thread or simply as a temporary was released. Take a `list()` of the items first when you need a stable snapshot.

  • Why can a WeakKeyDictionary still leak, and how do you spot it?
    Its values are held strongly. If a value refers back to its own key — a wrapper object, a closure capturing it, a record embedding it — then the mapping pins the value, the value pins the key, and the key can never be collected, so the entry is permanent. Spot it by checking whether the value's transitive references reach the key; the fix is to store plain data, or make the back-reference itself a weak reference.
  • What kinds of objects are rejected as keys of a weakref.WeakKeyDictionary?
    Anything that is not weak-referenceable, which includes the common immutable builtins: `str`, `int`, `tuple`, `bytes` and a bare `object()` all raise `TypeError`. Keys must also be hashable, as in any dict. In practice keys are instances of classes defined in Python, which are both hashable by identity and weak-referenceable by default.
  • Why is iterating a WeakValueDictionary riskier than iterating a plain dict?
    Entries can disappear part-way through, because a value's last strong reference may be released elsewhere — another thread, or simply a temporary going out of scope — and the mapping then drops that entry. Take a snapshot with `list(mapping.items())` before iterating when you need stability, and remember that the snapshot itself holds strong references and so pins those values for its lifetime.

saying these in an interview costs you the question

  • Thinks WeakValueDictionary drops entries when the key dies
  • Assumes a WeakKeyDictionary cannot leak because it is weak
  • Uses strings or ints as WeakKeyDictionary keys
  • Expects a weak mapping to keep a freshly created value alive
  • Treats a weak mapping as a size-bounded eviction policy

context