python/cpython · error · KeyError

pop from empty WeakSet

Error message

pop from empty WeakSet

What it means

WeakSet.pop loops popping references from the internal set self.data; if that set is empty the underlying set.pop raises KeyError, which is re-raised (chained suppression via `from None`) as KeyError('pop from empty WeakSet'). The loop also skips dead references, so a set holding only dead weakrefs also behaves as empty.

Source

Thrown at Lib/_weakrefset.py:59

    def __reduce__(self):
        return self.__class__, (list(self),), self.__getstate__()

    def add(self, item):
        self.data.add(ref(item, self._remove))

    def clear(self):
        self.data.clear()

    def copy(self):
        return self.__class__(self)

    def pop(self):
        while True:
            try:
                itemref = self.data.pop()
            except KeyError:
                raise KeyError('pop from empty WeakSet') from None
            item = itemref()
            if item is not None:
                return item

    def remove(self, item):
        self.data.remove(ref(item))

    def discard(self, item):
        self.data.discard(ref(item))

    def update(self, other):
        for element in other:
            self.add(element)

    def __ior__(self, other):
        self.update(other)
        return self

View on GitHub (pinned to bc6749cc3b)

Solutions

  1. Guard with a length/emptiness check, tolerating the GC race: try: item = ws.pop() except KeyError: handle-empty
  2. Prefer discard/remove or iteration when order does not matter
  3. Keep strong references to items that must survive (e.g. a companion list) so GC cannot empty the set unexpectedly

Example fix

# before
item = ws.pop()  # KeyError: pop from empty WeakSet

# after
try:
    item = ws.pop()
except KeyError:
    item = None
Defensive patterns

Strategy: try-catch

Validate before calling

def weakset_pop(ws):
    try:
        return ws.pop()
    except KeyError:
        return None  # or a sentinel / default

Type guard

def weakset_alive(ws) -> list:
    return [r for r in ws if r is not None]

Try / catch

try:
    item = observers.pop()
except KeyError:  # includes 'pop from empty WeakSet'
    item = None

Prevention

When it happens

Trigger: WeakSet().pop(); or .pop() on a WeakSet whose elements were garbage-collected (all remaining refs are dead) — mirroring set.pop semantics for emptiness.

Common situations: Using WeakSet for caches/observer registries and popping without checking; entries silently removed by GC make 'len()>0 just checked' races common in observer patterns.

Related errors


AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14). Data as JSON: /api/errors/822980b9d3b2a1b9. Report an issue: GitHub.