https://github.com/python/cpython/commit/8759666e4c00d7b7bcf47bf44dd470b04ab5f708
commit: 8759666e4c00d7b7bcf47bf44dd470b04ab5f708
branch: main
author: Eundo Lee <[email protected]>
committer: hugovk <[email protected]>
date: 2026-09-17T17:55:32+03:00
summary:
gh-155596: Fix pprint expand mode ignoring width for nested values (#155926)
files:
A Misc/NEWS.d/next/Library/2026-08-17-12-07-38.gh-issue-155596.Rt4mZq.rst
M Lib/pprint.py
M Lib/test/test_pprint.py
diff --git a/Lib/pprint.py b/Lib/pprint.py
index 7355021998081d..6b574218277a57 100644
--- a/Lib/pprint.py
+++ b/Lib/pprint.py
@@ -187,6 +187,11 @@ def isreadable(self, object):
return readable and not recursive
def _format(self, object, stream, indent, allowance, context, level):
+ # Width of any "key: " prefix already written on the current line by
+ # _format_child(). In expand mode `indent` is the block indent and so
+ # does not include it, but it still consumes width here.
+ prefix_len = self._pending_prefix_len
+ self._pending_prefix_len = 0
objid = id(object)
if objid in context:
stream.write(_recursion(object))
@@ -194,7 +199,7 @@ 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
+ max_width = self._width - indent - prefix_len - allowance
if len(rep) > max_width:
p = self._dispatch.get(type(object).__repr__, None)
# Lazy import to improve module import time
@@ -232,6 +237,22 @@ def _child_indent(self, indent, prefix_len):
return indent
return indent + prefix_len
+ # Set by _format_child() immediately before it calls _format(), and
+ # consumed there. Passing it out of band keeps _format()'s signature
+ # unchanged for third-party subclasses that override it.
+ _pending_prefix_len = 0
+
+ def _format_child(self, object, stream, indent, allowance, context, level,
+ prefix_len):
+ if self._expand:
+ # Aligned mode folds the prefix into the indent (see
+ # _child_indent), so only expand mode needs to report it.
+ self._pending_prefix_len = prefix_len
+ try:
+ self._format(object, stream, indent, allowance, context, level)
+ finally:
+ self._pending_prefix_len = 0
+
def _write_indent_padding(self, write):
if self._expand:
if self._indent_per_level > 0:
@@ -303,13 +324,14 @@ def _pprint_ordered_dict(self, object, stream, indent,
allowance, context, level
return
cls = object.__class__
stream.write(cls.__name__ + '(')
- self._format(
+ self._format_child(
list(object.items()),
stream,
self._child_indent(indent, len(cls.__name__) + 1),
allowance + 1,
context,
level,
+ len(cls.__name__) + 1,
)
stream.write(')')
@@ -498,13 +520,14 @@ def _pprint_bytearray(self, object, stream, indent,
allowance, context, level):
def _pprint_mappingproxy(self, object, stream, indent, allowance, context,
level):
stream.write('mappingproxy(')
- self._format(
+ self._format_child(
object.copy(),
stream,
self._child_indent(indent, 13),
allowance + 1,
context,
level,
+ 13,
)
stream.write(')')
@@ -540,13 +563,14 @@ def _format_dict_items(self, items, stream, indent,
allowance, context,
rep = self._repr(key, context, level)
write(rep)
write(': ')
- self._format(
+ self._format_child(
ent,
stream,
self._child_indent(indent, len(rep) + 2),
allowance if last else 1,
context,
level,
+ len(rep) + 2,
)
if not last:
write(delimnl)
@@ -566,13 +590,14 @@ def _format_namespace_items(self, items, stream, indent,
allowance, context, lev
# recursive dataclass repr.
write("...")
else:
- self._format(
+ self._format_child(
ent,
stream,
self._child_indent(indent, len(key) + 1),
allowance if last else 1,
context,
level,
+ len(key) + 1,
)
if not last:
write(delimnl)
diff --git a/Lib/test/test_pprint.py b/Lib/test/test_pprint.py
index 041c2072b9e253..dcb1cd693074cb 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 00000000000000..cbd8bc83868513
--- /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.
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]