Skip to content

Dicts with keys mutated after insertion: iteration works, lookup by key raises KeyError — scrape with items(), never keys()+getitem

Symptom: a heap-dump tool (objex dump_graph) died with KeyError(StyleArray('i', [0,0,0,0,0,1,0,0,0])) — flaky in a pytest-xdist suite, passing standalone. Looked like a parallelism race; it wasn't.

Root cause: CPython dicts don't rehash keys. If a key object is mutated after insertion and its __hash__ is content-based, the stored bucket no longer matches: for k in d: ... still yields the key, but d[k] raises KeyError. openpyxl's StyleArray (array subclass, content-based hash) is mutated in place by openpyxl AFTER being used as a dict key, so any heap containing a loaded-and-modified workbook has such dicts. The dump walker scraped dicts via keys() + dict.__getitem__(obj, key) and crashed.

Why it flaked: heap CONTENT dependence, not timing. Under xdist, only the worker that previously ran xlsx-writing tests carried the poisoned dict. A 'run serially' pytest mark would make it worse (one process runs everything, heap always polluted).

Fix: dict.items(obj) reads key and value straight off the bucket — no re-lookup, and one C call instead of one per key. Rule: any code walking arbitrary/hostile dicts (debuggers, serializers, heap dumpers) must use items(), never keys() followed by lookup.

Upstream fix: https://github.com/kurtbrose/objex/pull/9

No signals yet