Commit a7da1663 authored by Raymond Hettinger's avatar Raymond Hettinger

Issue 3976: fix pprint for sets, frozensets, and dicts containing unorderable types.

parent df961cfb
...@@ -70,6 +70,32 @@ def isrecursive(object): ...@@ -70,6 +70,32 @@ def isrecursive(object):
"""Determine if object requires a recursive representation.""" """Determine if object requires a recursive representation."""
return _safe_repr(object, {}, None, 0)[2] return _safe_repr(object, {}, None, 0)[2]
class _safe_key:
"""Helper function for key functions when sorting unorderable objects.
The wrapped-object will fallback to an Py2.x style comparison for
unorderable types (sorting first comparing the type name and then by
the obj ids). Does not work recursively, so dict.items() must have
_safe_key applied to both the key and the value.
"""
__slots__ = ['obj']
def __init__(self, obj):
self.obj = obj
def __lt__(self, other):
rv = self.obj.__lt__(other.obj)
if rv is NotImplemented:
rv = (str(type(self.obj)), id(self.obj)) < \
(str(type(other.obj)), id(other.obj))
return rv
def _safe_tuple(t):
"Helper function for comparing 2-tuples"
return _safe_key(t[0]), _safe_key(t[1])
class PrettyPrinter: class PrettyPrinter:
def __init__(self, indent=1, width=80, depth=None, stream=None): def __init__(self, indent=1, width=80, depth=None, stream=None):
"""Handle pretty printing operations onto a stream using a set of """Handle pretty printing operations onto a stream using a set of
...@@ -145,7 +171,7 @@ class PrettyPrinter: ...@@ -145,7 +171,7 @@ class PrettyPrinter:
if length: if length:
context[objid] = 1 context[objid] = 1
indent = indent + self._indent_per_level indent = indent + self._indent_per_level
items = sorted(object.items()) items = sorted(object.items(), key=_safe_tuple)
key, ent = items[0] key, ent = items[0]
rep = self._repr(key, context, level) rep = self._repr(key, context, level)
write(rep) write(rep)
...@@ -178,14 +204,14 @@ class PrettyPrinter: ...@@ -178,14 +204,14 @@ class PrettyPrinter:
return return
write('{') write('{')
endchar = '}' endchar = '}'
object = sorted(object) object = sorted(object, key=_safe_key)
elif issubclass(typ, frozenset): elif issubclass(typ, frozenset):
if not length: if not length:
write('frozenset()') write('frozenset()')
return return
write('frozenset({') write('frozenset({')
endchar = '})' endchar = '})'
object = sorted(object) object = sorted(object, key=_safe_key)
indent += 10 indent += 10
else: else:
write('(') write('(')
...@@ -267,14 +293,7 @@ def _safe_repr(object, context, maxlevels, level): ...@@ -267,14 +293,7 @@ def _safe_repr(object, context, maxlevels, level):
append = components.append append = components.append
level += 1 level += 1
saferepr = _safe_repr saferepr = _safe_repr
items = object.items() items = sorted(object.items(), key=_safe_tuple)
try:
items = sorted(items)
except TypeError:
def sortkey(item):
key, value = item
return str(type(key)), key, value
items = sorted(items, key=sortkey)
for k, v in items: for k, v in items:
krepr, kreadable, krecur = saferepr(k, context, maxlevels, level) krepr, kreadable, krecur = saferepr(k, context, maxlevels, level)
vrepr, vreadable, vrecur = saferepr(v, context, maxlevels, level) vrepr, vreadable, vrecur = saferepr(v, context, maxlevels, level)
......
...@@ -2,6 +2,7 @@ import pprint ...@@ -2,6 +2,7 @@ import pprint
import test.support import test.support
import unittest import unittest
import test.test_set import test.test_set
import random
# list, tuple and dict subclasses that do or don't overwrite __repr__ # list, tuple and dict subclasses that do or don't overwrite __repr__
class list2(list): class list2(list):
...@@ -25,6 +26,10 @@ class dict3(dict): ...@@ -25,6 +26,10 @@ class dict3(dict):
def __repr__(self): def __repr__(self):
return dict.__repr__(self) return dict.__repr__(self)
class Unorderable:
def __repr__(self):
return str(id(self))
class QueryTestCase(unittest.TestCase): class QueryTestCase(unittest.TestCase):
def setUp(self): def setUp(self):
...@@ -407,6 +412,20 @@ class QueryTestCase(unittest.TestCase): ...@@ -407,6 +412,20 @@ class QueryTestCase(unittest.TestCase):
self.assertEqual(pprint.pformat(nested_dict, depth=1), lv1_dict) self.assertEqual(pprint.pformat(nested_dict, depth=1), lv1_dict)
self.assertEqual(pprint.pformat(nested_list, depth=1), lv1_list) self.assertEqual(pprint.pformat(nested_list, depth=1), lv1_list)
def test_sort_unorderable_values(self):
# Issue 3976: sorted pprints fail for unorderable values.
n = 20
keys = [Unorderable() for i in range(n)]
random.shuffle(keys)
skeys = sorted(keys, key=id)
clean = lambda s: s.replace(' ', '').replace('\n','')
self.assertEqual(clean(pprint.pformat(set(keys))),
'{' + ','.join(map(repr, skeys)) + '}')
self.assertEqual(clean(pprint.pformat(frozenset(keys))),
'frozenset({' + ','.join(map(repr, skeys)) + '})')
self.assertEqual(clean(pprint.pformat(dict.fromkeys(keys))),
'{' + ','.join('%r:None' % k for k in skeys) + '}')
class DottedPrettyPrinter(pprint.PrettyPrinter): class DottedPrettyPrinter(pprint.PrettyPrinter):
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment