bpo-28850: Fix PrettyPrinter.format overrides ignored for contents of small containers (GH-22120)

This commit is contained in:
Irit Katriel 2020-11-23 13:31:31 +00:00 committed by GitHub
parent dd844a2916
commit ff420f0e08
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 88 additions and 74 deletions

View File

@ -64,15 +64,15 @@ def pp(object, *args, sort_dicts=False, **kwargs):
def saferepr(object): def saferepr(object):
"""Version of repr() which can handle recursive data structures.""" """Version of repr() which can handle recursive data structures."""
return _safe_repr(object, {}, None, 0, True)[0] return PrettyPrinter()._safe_repr(object, {}, None, 0)[0]
def isreadable(object): def isreadable(object):
"""Determine if saferepr(object) is readable by eval().""" """Determine if saferepr(object) is readable by eval()."""
return _safe_repr(object, {}, None, 0, True)[1] return PrettyPrinter()._safe_repr(object, {}, None, 0)[1]
def isrecursive(object): def isrecursive(object):
"""Determine if object requires a recursive representation.""" """Determine if object requires a recursive representation."""
return _safe_repr(object, {}, None, 0, True)[2] return PrettyPrinter()._safe_repr(object, {}, None, 0)[2]
class _safe_key: class _safe_key:
"""Helper function for key functions when sorting unorderable objects. """Helper function for key functions when sorting unorderable objects.
@ -435,7 +435,7 @@ class PrettyPrinter:
and flags indicating whether the representation is 'readable' and flags indicating whether the representation is 'readable'
and whether the object represents a recursive construct. and whether the object represents a recursive construct.
""" """
return _safe_repr(object, context, maxlevels, level, self._sort_dicts) return self._safe_repr(object, context, maxlevels, level)
def _pprint_default_dict(self, object, stream, indent, allowance, context, level): def _pprint_default_dict(self, object, stream, indent, allowance, context, level):
if not len(object): if not len(object):
@ -518,77 +518,79 @@ class PrettyPrinter:
_dispatch[_collections.UserString.__repr__] = _pprint_user_string _dispatch[_collections.UserString.__repr__] = _pprint_user_string
# Return triple (repr_string, isreadable, isrecursive). def _safe_repr(self, object, context, maxlevels, level):
# Return triple (repr_string, isreadable, isrecursive).
typ = type(object)
if typ in _builtin_scalars:
return repr(object), True, False
def _safe_repr(object, context, maxlevels, level, sort_dicts): r = getattr(typ, "__repr__", None)
typ = type(object) if issubclass(typ, dict) and r is dict.__repr__:
if typ in _builtin_scalars:
return repr(object), True, False
r = getattr(typ, "__repr__", None)
if issubclass(typ, dict) and r is dict.__repr__:
if not object:
return "{}", True, False
objid = id(object)
if maxlevels and level >= maxlevels:
return "{...}", False, objid in context
if objid in context:
return _recursion(object), False, True
context[objid] = 1
readable = True
recursive = False
components = []
append = components.append
level += 1
if sort_dicts:
items = sorted(object.items(), key=_safe_tuple)
else:
items = object.items()
for k, v in items:
krepr, kreadable, krecur = _safe_repr(k, context, maxlevels, level, sort_dicts)
vrepr, vreadable, vrecur = _safe_repr(v, context, maxlevels, level, sort_dicts)
append("%s: %s" % (krepr, vrepr))
readable = readable and kreadable and vreadable
if krecur or vrecur:
recursive = True
del context[objid]
return "{%s}" % ", ".join(components), readable, recursive
if (issubclass(typ, list) and r is list.__repr__) or \
(issubclass(typ, tuple) and r is tuple.__repr__):
if issubclass(typ, list):
if not object: if not object:
return "[]", True, False return "{}", True, False
format = "[%s]" objid = id(object)
elif len(object) == 1: if maxlevels and level >= maxlevels:
format = "(%s,)" return "{...}", False, objid in context
else: if objid in context:
if not object: return _recursion(object), False, True
return "()", True, False context[objid] = 1
format = "(%s)" readable = True
objid = id(object) recursive = False
if maxlevels and level >= maxlevels: components = []
return format % "...", False, objid in context append = components.append
if objid in context: level += 1
return _recursion(object), False, True if self._sort_dicts:
context[objid] = 1 items = sorted(object.items(), key=_safe_tuple)
readable = True else:
recursive = False items = object.items()
components = [] for k, v in items:
append = components.append krepr, kreadable, krecur = self.format(
level += 1 k, context, maxlevels, level)
for o in object: vrepr, vreadable, vrecur = self.format(
orepr, oreadable, orecur = _safe_repr(o, context, maxlevels, level, sort_dicts) v, context, maxlevels, level)
append(orepr) append("%s: %s" % (krepr, vrepr))
if not oreadable: readable = readable and kreadable and vreadable
readable = False if krecur or vrecur:
if orecur: recursive = True
recursive = True del context[objid]
del context[objid] return "{%s}" % ", ".join(components), readable, recursive
return format % ", ".join(components), readable, recursive
rep = repr(object) if (issubclass(typ, list) and r is list.__repr__) or \
return rep, (rep and not rep.startswith('<')), False (issubclass(typ, tuple) and r is tuple.__repr__):
if issubclass(typ, list):
if not object:
return "[]", True, False
format = "[%s]"
elif len(object) == 1:
format = "(%s,)"
else:
if not object:
return "()", True, False
format = "(%s)"
objid = id(object)
if maxlevels and level >= maxlevels:
return format % "...", False, objid in context
if objid in context:
return _recursion(object), False, True
context[objid] = 1
readable = True
recursive = False
components = []
append = components.append
level += 1
for o in object:
orepr, oreadable, orecur = self.format(
o, context, maxlevels, level)
append(orepr)
if not oreadable:
readable = False
if orecur:
recursive = True
del context[objid]
return format % ", ".join(components), readable, recursive
rep = repr(object)
return rep, (rep and not rep.startswith('<')), False
_builtin_scalars = frozenset({str, bytes, bytearray, int, float, complex, _builtin_scalars = frozenset({str, bytes, bytearray, int, float, complex,
bool, type(None)}) bool, type(None)})
@ -604,7 +606,7 @@ def _perfcheck(object=None):
object = [("string", (1, 2), [3, 4], {5: 6, 7: 8})] * 100000 object = [("string", (1, 2), [3, 4], {5: 6, 7: 8})] * 100000
p = PrettyPrinter() p = PrettyPrinter()
t1 = time.perf_counter() t1 = time.perf_counter()
_safe_repr(object, {}, None, 0, True) p._safe_repr(object, {}, None, 0, True)
t2 = time.perf_counter() t2 = time.perf_counter()
p.pformat(object) p.pformat(object)
t3 = time.perf_counter() t3 = time.perf_counter()

View File

@ -453,12 +453,23 @@ AdvancedNamespace(the=0,
dog=8)""") dog=8)""")
def test_subclassing(self): def test_subclassing(self):
# length(repr(obj)) > width
o = {'names with spaces': 'should be presented using repr()', o = {'names with spaces': 'should be presented using repr()',
'others.should.not.be': 'like.this'} 'others.should.not.be': 'like.this'}
exp = """\ exp = """\
{'names with spaces': 'should be presented using repr()', {'names with spaces': 'should be presented using repr()',
others.should.not.be: like.this}""" others.should.not.be: like.this}"""
self.assertEqual(DottedPrettyPrinter().pformat(o), exp)
dotted_printer = DottedPrettyPrinter()
self.assertEqual(dotted_printer.pformat(o), exp)
# length(repr(obj)) < width
o1 = ['with space']
exp1 = "['with space']"
self.assertEqual(dotted_printer.pformat(o1), exp1)
o2 = ['without.space']
exp2 = "[without.space]"
self.assertEqual(dotted_printer.pformat(o2), exp2)
def test_set_reprs(self): def test_set_reprs(self):
self.assertEqual(pprint.pformat(set()), 'set()') self.assertEqual(pprint.pformat(set()), 'set()')

View File

@ -0,0 +1 @@
Fix :meth:`pprint.PrettyPrinter.format` overrides being ignored for contents of small containers. The :func:`pprint._safe_repr` function was removed.