Skip to content

Commit d03a7d3

Browse files
committed
gh-155596: Fix pprint expand mode ignoring width for nested values
1 parent 50fcb91 commit d03a7d3

3 files changed

Lines changed: 53 additions & 8 deletions

File tree

Lib/pprint.py

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -186,15 +186,24 @@ def isreadable(self, object):
186186
s, readable, recursive = self.format(object, {}, 0, 0)
187187
return readable and not recursive
188188

189-
def _format(self, object, stream, indent, allowance, context, level):
189+
def _format(self, object, stream, indent, allowance, context, level,
190+
prefix_len=0):
190191
objid = id(object)
191192
if objid in context:
192193
stream.write(_recursion(object))
193194
self._recursive = True
194195
self._readable = False
195196
return
196197
rep = self._repr(object, context, level)
197-
max_width = self._width - indent - allowance
198+
# prefix_len is the width of any "key: " or "name=" already written on
199+
# this line. In aligned mode continuation lines start after it, so it
200+
# is folded into the indent. In expand mode children are indented at
201+
# the block level instead, but the prefix still consumes width here.
202+
if self._expand:
203+
max_width = self._width - indent - prefix_len - allowance
204+
else:
205+
indent += prefix_len
206+
max_width = self._width - indent - allowance
198207
if len(rep) > max_width:
199208
p = self._dispatch.get(type(object).__repr__, None)
200209
# Lazy import to improve module import time
@@ -306,10 +315,11 @@ def _pprint_ordered_dict(self, object, stream, indent, allowance, context, level
306315
self._format(
307316
list(object.items()),
308317
stream,
309-
self._child_indent(indent, len(cls.__name__) + 1),
318+
indent,
310319
allowance + 1,
311320
context,
312321
level,
322+
prefix_len=len(cls.__name__) + 1,
313323
)
314324
stream.write(')')
315325

@@ -501,10 +511,11 @@ def _pprint_mappingproxy(self, object, stream, indent, allowance, context, level
501511
self._format(
502512
object.copy(),
503513
stream,
504-
self._child_indent(indent, 13),
514+
indent,
505515
allowance + 1,
506516
context,
507517
level,
518+
prefix_len=13,
508519
)
509520
stream.write(')')
510521

@@ -543,10 +554,11 @@ def _format_dict_items(self, items, stream, indent, allowance, context,
543554
self._format(
544555
ent,
545556
stream,
546-
self._child_indent(indent, len(rep) + 2),
557+
indent,
547558
allowance if last else 1,
548559
context,
549560
level,
561+
prefix_len=len(rep) + 2,
550562
)
551563
if not last:
552564
write(delimnl)
@@ -569,10 +581,11 @@ def _format_namespace_items(self, items, stream, indent, allowance, context, lev
569581
self._format(
570582
ent,
571583
stream,
572-
self._child_indent(indent, len(key) + 1),
584+
indent,
573585
allowance if last else 1,
574586
context,
575587
level,
588+
prefix_len=len(key) + 1,
576589
)
577590
if not last:
578591
write(delimnl)

Lib/test/test_pprint.py

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1646,6 +1646,26 @@ def test_expand_dict(self):
16461646
'corge': 7,
16471647
}""")
16481648

1649+
def test_expand_respects_width_with_long_keys(self):
1650+
# gh-155596: in expand mode the width of the "key: " prefix was not
1651+
# counted when deciding whether a value fits on the current line, so
1652+
# values under long keys could overflow width.
1653+
obj = {'a' * 12: 1, 'b' * 20: 2, 'c' * 30: {'d' * 5: 3, 'e' * 40: 3}}
1654+
result = pprint.pformat(obj, expand=True)
1655+
self.assertEqual(result,
1656+
"""\
1657+
{
1658+
'aaaaaaaaaaaa': 1,
1659+
'bbbbbbbbbbbbbbbbbbbb': 2,
1660+
'cccccccccccccccccccccccccccccc': {
1661+
'ddddd': 3,
1662+
'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee': 3,
1663+
},
1664+
}""")
1665+
# The nested value must be broken up rather than overflowing.
1666+
self.assertTrue(all(len(line) <= 80 for line in result.splitlines()),
1667+
max(result.splitlines(), key=len))
1668+
16491669
def test_expand_ordered_dict(self):
16501670
dummy_ordered_dict = collections.OrderedDict(
16511671
[
@@ -1895,7 +1915,11 @@ def test_expand_chainmap(self):
18951915
'baz': 123,
18961916
'corge': 7,
18971917
'foo': 'bar',
1898-
'quux': ['foo', 'bar', 'baz'],
1918+
'quux': [
1919+
'foo',
1920+
'bar',
1921+
'baz',
1922+
],
18991923
'qux': {
19001924
'baz': 123,
19011925
'foo': 'bar',
@@ -1939,7 +1963,10 @@ def test_expand_deque(self):
19391963
'corge': 7,
19401964
'foo': 'bar',
19411965
'quux': ['foo', 'bar', 'baz'],
1942-
'qux': {'baz': 123, 'foo': 'bar'},
1966+
'qux': {
1967+
'baz': 123,
1968+
'foo': 'bar',
1969+
},
19431970
},
19441971
'foo',
19451972
'bar',
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Fix :func:`pprint.pprint` and :func:`pprint.pformat` with ``expand=True``
2+
not honouring *width* for nested values. The width of the ``'key':``
3+
prefix was not counted when deciding whether a value fitted on the current
4+
line, so values under long keys could overflow *width* instead of being
5+
expanded.

0 commit comments

Comments
 (0)