Skip to content

Commit ff420f0

Browse files
authored
bpo-28850: Fix PrettyPrinter.format overrides ignored for contents of small containers (pythonGH-22120)
1 parent dd844a2 commit ff420f0

File tree

3 files changed

+89
-75
lines changed

3 files changed

+89
-75
lines changed

Lib/pprint.py

+76-74
Original file line numberDiff line numberDiff line change
@@ -64,15 +64,15 @@ def pp(object, *args, sort_dicts=False, **kwargs):
6464

6565
def saferepr(object):
6666
"""Version of repr() which can handle recursive data structures."""
67-
return _safe_repr(object, {}, None, 0, True)[0]
67+
return PrettyPrinter()._safe_repr(object, {}, None, 0)[0]
6868

6969
def isreadable(object):
7070
"""Determine if saferepr(object) is readable by eval()."""
71-
return _safe_repr(object, {}, None, 0, True)[1]
71+
return PrettyPrinter()._safe_repr(object, {}, None, 0)[1]
7272

7373
def isrecursive(object):
7474
"""Determine if object requires a recursive representation."""
75-
return _safe_repr(object, {}, None, 0, True)[2]
75+
return PrettyPrinter()._safe_repr(object, {}, None, 0)[2]
7676

7777
class _safe_key:
7878
"""Helper function for key functions when sorting unorderable objects.
@@ -435,7 +435,7 @@ def format(self, object, context, maxlevels, level):
435435
and flags indicating whether the representation is 'readable'
436436
and whether the object represents a recursive construct.
437437
"""
438-
return _safe_repr(object, context, maxlevels, level, self._sort_dicts)
438+
return self._safe_repr(object, context, maxlevels, level)
439439

440440
def _pprint_default_dict(self, object, stream, indent, allowance, context, level):
441441
if not len(object):
@@ -518,77 +518,79 @@ def _pprint_user_string(self, object, stream, indent, allowance, context, level)
518518

519519
_dispatch[_collections.UserString.__repr__] = _pprint_user_string
520520

521-
# Return triple (repr_string, isreadable, isrecursive).
521+
def _safe_repr(self, object, context, maxlevels, level):
522+
# Return triple (repr_string, isreadable, isrecursive).
523+
typ = type(object)
524+
if typ in _builtin_scalars:
525+
return repr(object), True, False
522526

523-
def _safe_repr(object, context, maxlevels, level, sort_dicts):
524-
typ = type(object)
525-
if typ in _builtin_scalars:
526-
return repr(object), True, False
527-
528-
r = getattr(typ, "__repr__", None)
529-
if issubclass(typ, dict) and r is dict.__repr__:
530-
if not object:
531-
return "{}", True, False
532-
objid = id(object)
533-
if maxlevels and level >= maxlevels:
534-
return "{...}", False, objid in context
535-
if objid in context:
536-
return _recursion(object), False, True
537-
context[objid] = 1
538-
readable = True
539-
recursive = False
540-
components = []
541-
append = components.append
542-
level += 1
543-
if sort_dicts:
544-
items = sorted(object.items(), key=_safe_tuple)
545-
else:
546-
items = object.items()
547-
for k, v in items:
548-
krepr, kreadable, krecur = _safe_repr(k, context, maxlevels, level, sort_dicts)
549-
vrepr, vreadable, vrecur = _safe_repr(v, context, maxlevels, level, sort_dicts)
550-
append("%s: %s" % (krepr, vrepr))
551-
readable = readable and kreadable and vreadable
552-
if krecur or vrecur:
553-
recursive = True
554-
del context[objid]
555-
return "{%s}" % ", ".join(components), readable, recursive
556-
557-
if (issubclass(typ, list) and r is list.__repr__) or \
558-
(issubclass(typ, tuple) and r is tuple.__repr__):
559-
if issubclass(typ, list):
527+
r = getattr(typ, "__repr__", None)
528+
if issubclass(typ, dict) and r is dict.__repr__:
560529
if not object:
561-
return "[]", True, False
562-
format = "[%s]"
563-
elif len(object) == 1:
564-
format = "(%s,)"
565-
else:
566-
if not object:
567-
return "()", True, False
568-
format = "(%s)"
569-
objid = id(object)
570-
if maxlevels and level >= maxlevels:
571-
return format % "...", False, objid in context
572-
if objid in context:
573-
return _recursion(object), False, True
574-
context[objid] = 1
575-
readable = True
576-
recursive = False
577-
components = []
578-
append = components.append
579-
level += 1
580-
for o in object:
581-
orepr, oreadable, orecur = _safe_repr(o, context, maxlevels, level, sort_dicts)
582-
append(orepr)
583-
if not oreadable:
584-
readable = False
585-
if orecur:
586-
recursive = True
587-
del context[objid]
588-
return format % ", ".join(components), readable, recursive
589-
590-
rep = repr(object)
591-
return rep, (rep and not rep.startswith('<')), False
530+
return "{}", True, False
531+
objid = id(object)
532+
if maxlevels and level >= maxlevels:
533+
return "{...}", False, objid in context
534+
if objid in context:
535+
return _recursion(object), False, True
536+
context[objid] = 1
537+
readable = True
538+
recursive = False
539+
components = []
540+
append = components.append
541+
level += 1
542+
if self._sort_dicts:
543+
items = sorted(object.items(), key=_safe_tuple)
544+
else:
545+
items = object.items()
546+
for k, v in items:
547+
krepr, kreadable, krecur = self.format(
548+
k, context, maxlevels, level)
549+
vrepr, vreadable, vrecur = self.format(
550+
v, context, maxlevels, level)
551+
append("%s: %s" % (krepr, vrepr))
552+
readable = readable and kreadable and vreadable
553+
if krecur or vrecur:
554+
recursive = True
555+
del context[objid]
556+
return "{%s}" % ", ".join(components), readable, recursive
557+
558+
if (issubclass(typ, list) and r is list.__repr__) or \
559+
(issubclass(typ, tuple) and r is tuple.__repr__):
560+
if issubclass(typ, list):
561+
if not object:
562+
return "[]", True, False
563+
format = "[%s]"
564+
elif len(object) == 1:
565+
format = "(%s,)"
566+
else:
567+
if not object:
568+
return "()", True, False
569+
format = "(%s)"
570+
objid = id(object)
571+
if maxlevels and level >= maxlevels:
572+
return format % "...", False, objid in context
573+
if objid in context:
574+
return _recursion(object), False, True
575+
context[objid] = 1
576+
readable = True
577+
recursive = False
578+
components = []
579+
append = components.append
580+
level += 1
581+
for o in object:
582+
orepr, oreadable, orecur = self.format(
583+
o, context, maxlevels, level)
584+
append(orepr)
585+
if not oreadable:
586+
readable = False
587+
if orecur:
588+
recursive = True
589+
del context[objid]
590+
return format % ", ".join(components), readable, recursive
591+
592+
rep = repr(object)
593+
return rep, (rep and not rep.startswith('<')), False
592594

593595
_builtin_scalars = frozenset({str, bytes, bytearray, int, float, complex,
594596
bool, type(None)})
@@ -604,7 +606,7 @@ def _perfcheck(object=None):
604606
object = [("string", (1, 2), [3, 4], {5: 6, 7: 8})] * 100000
605607
p = PrettyPrinter()
606608
t1 = time.perf_counter()
607-
_safe_repr(object, {}, None, 0, True)
609+
p._safe_repr(object, {}, None, 0, True)
608610
t2 = time.perf_counter()
609611
p.pformat(object)
610612
t3 = time.perf_counter()

Lib/test/test_pprint.py

+12-1
Original file line numberDiff line numberDiff line change
@@ -453,12 +453,23 @@ class AdvancedNamespace(types.SimpleNamespace): pass
453453
dog=8)""")
454454

455455
def test_subclassing(self):
456+
# length(repr(obj)) > width
456457
o = {'names with spaces': 'should be presented using repr()',
457458
'others.should.not.be': 'like.this'}
458459
exp = """\
459460
{'names with spaces': 'should be presented using repr()',
460461
others.should.not.be: like.this}"""
461-
self.assertEqual(DottedPrettyPrinter().pformat(o), exp)
462+
463+
dotted_printer = DottedPrettyPrinter()
464+
self.assertEqual(dotted_printer.pformat(o), exp)
465+
466+
# length(repr(obj)) < width
467+
o1 = ['with space']
468+
exp1 = "['with space']"
469+
self.assertEqual(dotted_printer.pformat(o1), exp1)
470+
o2 = ['without.space']
471+
exp2 = "[without.space]"
472+
self.assertEqual(dotted_printer.pformat(o2), exp2)
462473

463474
def test_set_reprs(self):
464475
self.assertEqual(pprint.pformat(set()), 'set()')
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix :meth:`pprint.PrettyPrinter.format` overrides being ignored for contents of small containers. The :func:`pprint._safe_repr` function was removed.

0 commit comments

Comments
 (0)