From d03a7d34eddd784e37e7107331ce6b7088d9be2e Mon Sep 17 00:00:00 2001 From: Eundo Lee Date: Mon, 17 Aug 2026 12:22:10 +0900 Subject: [PATCH] gh-155596: Fix pprint expand mode ignoring width for nested values --- Lib/pprint.py | 25 +++++++++++---- Lib/test/test_pprint.py | 31 +++++++++++++++++-- ...-08-17-12-07-38.gh-issue-155596.Rt4mZq.rst | 5 +++ 3 files changed, 53 insertions(+), 8 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-08-17-12-07-38.gh-issue-155596.Rt4mZq.rst diff --git a/Lib/pprint.py b/Lib/pprint.py index 7355021998081dc..6253af37efc6717 100644 --- a/Lib/pprint.py +++ b/Lib/pprint.py @@ -186,7 +186,8 @@ def isreadable(self, object): s, readable, recursive = self.format(object, {}, 0, 0) return readable and not recursive - def _format(self, object, stream, indent, allowance, context, level): + def _format(self, object, stream, indent, allowance, context, level, + prefix_len=0): objid = id(object) if objid in context: stream.write(_recursion(object)) @@ -194,7 +195,15 @@ def _format(self, object, stream, indent, allowance, context, level): self._readable = False return rep = self._repr(object, context, level) - max_width = self._width - indent - allowance + # prefix_len is the width of any "key: " or "name=" already written on + # this line. In aligned mode continuation lines start after it, so it + # is folded into the indent. In expand mode children are indented at + # the block level instead, but the prefix still consumes width here. + if self._expand: + max_width = self._width - indent - prefix_len - allowance + else: + indent += prefix_len + max_width = self._width - indent - allowance if len(rep) > max_width: p = self._dispatch.get(type(object).__repr__, None) # Lazy import to improve module import time @@ -306,10 +315,11 @@ def _pprint_ordered_dict(self, object, stream, indent, allowance, context, level self._format( list(object.items()), stream, - self._child_indent(indent, len(cls.__name__) + 1), + indent, allowance + 1, context, level, + prefix_len=len(cls.__name__) + 1, ) stream.write(')') @@ -501,10 +511,11 @@ def _pprint_mappingproxy(self, object, stream, indent, allowance, context, level self._format( object.copy(), stream, - self._child_indent(indent, 13), + indent, allowance + 1, context, level, + prefix_len=13, ) stream.write(')') @@ -543,10 +554,11 @@ def _format_dict_items(self, items, stream, indent, allowance, context, self._format( ent, stream, - self._child_indent(indent, len(rep) + 2), + indent, allowance if last else 1, context, level, + prefix_len=len(rep) + 2, ) if not last: write(delimnl) @@ -569,10 +581,11 @@ def _format_namespace_items(self, items, stream, indent, allowance, context, lev self._format( ent, stream, - self._child_indent(indent, len(key) + 1), + indent, allowance if last else 1, context, level, + prefix_len=len(key) + 1, ) if not last: write(delimnl) diff --git a/Lib/test/test_pprint.py b/Lib/test/test_pprint.py index 041c2072b9e253a..dcb1cd693074cbb 100644 --- a/Lib/test/test_pprint.py +++ b/Lib/test/test_pprint.py @@ -1646,6 +1646,26 @@ def test_expand_dict(self): 'corge': 7, }""") + def test_expand_respects_width_with_long_keys(self): + # gh-155596: in expand mode the width of the "key: " prefix was not + # counted when deciding whether a value fits on the current line, so + # values under long keys could overflow width. + obj = {'a' * 12: 1, 'b' * 20: 2, 'c' * 30: {'d' * 5: 3, 'e' * 40: 3}} + result = pprint.pformat(obj, expand=True) + self.assertEqual(result, +"""\ +{ + 'aaaaaaaaaaaa': 1, + 'bbbbbbbbbbbbbbbbbbbb': 2, + 'cccccccccccccccccccccccccccccc': { + 'ddddd': 3, + 'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee': 3, + }, +}""") + # The nested value must be broken up rather than overflowing. + self.assertTrue(all(len(line) <= 80 for line in result.splitlines()), + max(result.splitlines(), key=len)) + def test_expand_ordered_dict(self): dummy_ordered_dict = collections.OrderedDict( [ @@ -1895,7 +1915,11 @@ def test_expand_chainmap(self): 'baz': 123, 'corge': 7, 'foo': 'bar', - 'quux': ['foo', 'bar', 'baz'], + 'quux': [ + 'foo', + 'bar', + 'baz', + ], 'qux': { 'baz': 123, 'foo': 'bar', @@ -1939,7 +1963,10 @@ def test_expand_deque(self): 'corge': 7, 'foo': 'bar', 'quux': ['foo', 'bar', 'baz'], - 'qux': {'baz': 123, 'foo': 'bar'}, + 'qux': { + 'baz': 123, + 'foo': 'bar', + }, }, 'foo', 'bar', diff --git a/Misc/NEWS.d/next/Library/2026-08-17-12-07-38.gh-issue-155596.Rt4mZq.rst b/Misc/NEWS.d/next/Library/2026-08-17-12-07-38.gh-issue-155596.Rt4mZq.rst new file mode 100644 index 000000000000000..cbd8bc838685139 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-08-17-12-07-38.gh-issue-155596.Rt4mZq.rst @@ -0,0 +1,5 @@ +Fix :func:`pprint.pprint` and :func:`pprint.pformat` with ``expand=True`` +not honouring *width* for nested values. The width of the ``'key':`` +prefix was not counted when deciding whether a value fitted on the current +line, so values under long keys could overflow *width* instead of being +expanded.