Repository navigation
Fix RuntimeError when pretty-printing a dict mutated during iteration - #15472
Open
bobby-nandigam wants to merge 1 commit into
Open
bobby-nandigam wants to merge 1 commit into
bobby-nandigam wants to merge 1 commit into
Conversation
The dict pretty-printer iterated a live key view (obj.keys()) and looked up each value with obj[key]. A mapping that grows while being formatted -- for example a module's __dict__ that populates lazily on first attribute access, as happens with vars(posix) on its first call -- therefore raised "RuntimeError: dictionary changed size during iteration". Snapshot the mapping's items with list(obj.items()) before iterating so the live mapping is read exactly once, up front. This also removes the second live lookup per key. Behaviour for ordinary, non-mutating mappings is unchanged. Add a regression test that formats a dict which grows while one of its values is being pretty-printed. Fixes ipython#14541
raidos23
reviewed
Nov 9, 2026
raidos23
left a comment
There was a problem hiding this comment.
Could we limit the snapshot to the number of items that can actually be formatted?
Currently, list(obj.items()) materializes the entire mapping before p._enumerate() applies the max_seq_length limit.
For very large dictionaries, this can result in unnecessary memory consumption compared to the previous implementation.
One possible approach would be to use itertools.islice() to snapshot only the required number of items, plus one extra item to preserve the truncation indicator. When max_seq_length is disabled, the full snapshot could still be retained
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #14541.
Pretty-printing a mapping that mutates while it is being formatted raised:
The reported trigger is
vars(posix)on its first call: a module's__dict__populates lazily the first time its attributes are accessed, so itgrows while the pretty-printer is iterating it (which is why it works on every
subsequent call, or after completion has already touched the attributes).
Root cause
_dict_pprinter_factoryinIPython/lib/pretty.pyiterated the mapping'slive key view and looked each value up again:
If anything mutates
objduring the loop, iterating the live view raisesRuntimeError.Fix
Snapshot the items once, up front, and iterate that snapshot:
The live mapping is now read exactly once, before any value is formatted, so a
mutation triggered during formatting can no longer break iteration. Capturing
(key, value)pairs also removes the second liveobj[key]lookup. Behaviourfor ordinary, non-mutating mappings (including
dict,os.environ, andtypes.MappingProxyType, which share this factory) is unchanged.Testing
test_dict_mutated_while_pretty_printing, which formats a dict thatgrows while one of its values is being pretty-printed. It reproduces the
exact
RuntimeErroronmainand passes with this change.pytest tests/test_pretty.py— all tests pass (54).ruff checkreports no new findings on the changed lines.Notes
Added a
docs/source/whatsnew/pr/fragment describing the fix.