Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 30 additions & 5 deletions Lib/pprint.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,14 +187,19 @@ 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))
self._recursive = True
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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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(')')

Expand Down Expand Up @@ -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(')')

Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down
31 changes: 29 additions & 2 deletions Lib/test/test_pprint.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
[
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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',
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Loading