diff -r e72aab080165 Lib/pprint.py --- a/Lib/pprint.py Sat Jul 23 11:16:56 2016 -0400 +++ b/Lib/pprint.py Sun Jul 24 17:15:41 2016 +0300 @@ -493,68 +493,201 @@ def _safe_repr(object, context, maxlevel 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 - saferepr = _safe_repr - items = sorted(object.items(), key=_safe_tuple) - for k, v in items: - krepr, kreadable, krecur = saferepr(k, context, maxlevels, level) - vrepr, vreadable, vrecur = saferepr(v, context, maxlevels, level) - 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: - 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 = _safe_repr(o, context, maxlevels, level) - append(orepr) - if not oreadable: - readable = False - if orecur: - recursive = True - del context[objid] - return format % ", ".join(components), readable, recursive + p = _safe_repr_dispatch.get(r, None) + if p is not None: + return p(object, context, maxlevels, level) rep = repr(object) return rep, (rep and not rep.startswith('<')), False +_safe_repr_dispatch = {} + +def _safe_repr_dict(object, context, maxlevels, level): + if not object: + return '{}', True, False + items = sorted(object.items(), key=_safe_tuple) + return _safe_repr_dict_items(object, items, '{', '}', context, maxlevels, level) + +_safe_repr_dispatch[dict.__repr__] = _safe_repr_dict + +def _safe_repr_ordered_dict(object, context, maxlevels, level): + if not object: + return object.__class__.__name__ + '()', True, False + items = object.items() + return _safe_repr_items(object, items, + object.__class__.__name__ + '([', '])', context, maxlevels, level) + +_safe_repr_dispatch[_collections.OrderedDict.__repr__] = _safe_repr_ordered_dict + +def _safe_repr_list(object, context, maxlevels, level): + if not object: + return '[]', True, False + return _safe_repr_items(object, object, '[', ']', context, maxlevels, level) + +_safe_repr_dispatch[list.__repr__] = _safe_repr_list + +def _safe_repr_tuple(object, context, maxlevels, level): + if len(object) == 1: + return _safe_repr_items(object, object, '(', ',)', context, maxlevels, level) + if not object: + return '()', True, False + return _safe_repr_items(object, object, '(', ')', context, maxlevels, level) + +_safe_repr_dispatch[tuple.__repr__] = _safe_repr_tuple + +def _safe_repr_set(object, context, maxlevels, level): + items = sorted(object, key=_safe_key) + if not items: + return repr(object), True, False + typ = object.__class__ + if typ is set: + prefix = '{' + suffix = '}' + else: + prefix = typ.__name__ + '({' + suffix = '})' + return _safe_repr_items(object, items, prefix, suffix, context, maxlevels, level) + +_safe_repr_dispatch[set.__repr__] = _safe_repr_set +_safe_repr_dispatch[frozenset.__repr__] = _safe_repr_set + +def _safe_repr_mappingproxy(object, context, maxlevels, level): + objid = id(object) + if maxlevels and level >= maxlevels: + return 'mappingproxy(...)', False, objid in context + if objid in context: + return _recursion(object), False, True + context[objid] = 1 + rep, readable, recursive = _safe_repr(object.copy(), context, maxlevels, level + 1) + del context[objid] + return 'mappingproxy(%s)' % rep, readable, recursive + +_safe_repr_dispatch[_types.MappingProxyType.__repr__] = _safe_repr_mappingproxy + +def _safe_repr_default_dict(object, context, maxlevels, level): + objid = id(object) + if maxlevels and level >= maxlevels: + return '%s(...)' % (object.__class__.__name__,), False, objid in context + if objid in context: + return _recursion(object), False, True + dfrep, dfreadable, dfrecur = _safe_repr(object.default_factory, context, maxlevels, level + 1) + rep, readable, recur = _safe_repr_dict(object, context, maxlevels, level + 1) + rep = '%s(%s, %s)' % (object.__class__.__name__, dfrep, rep) + return rep, dfreadable and readable, dfrecur or recur + +_safe_repr_dispatch[_collections.defaultdict.__repr__] = _safe_repr_default_dict + +def _safe_repr_counter(object, context, maxlevels, level): + if not object: + return repr(object), True, False + items = object.most_common() + return _safe_repr_dict_items(object, items, + object.__class__.__name__ + '({', '})', context, maxlevels, level) + +_safe_repr_dispatch[_collections.Counter.__repr__] = _safe_repr_counter + +def _safe_repr_chain_map(object, context, maxlevels, level): + objid = id(object) + if maxlevels and level >= maxlevels: + return '%s(...)' % (object.__class__.__name__,), False, objid in context + if objid in context: + return _recursion(object), False, True + context[objid] = 1 + components = [] + readable = True + recursive = False + for m in object.maps: + mrep, mreadable, mrecursive = _safe_repr(m, context, maxlevels, level + 1) + components.append(mrep) + readable = readable and mreadable + recursive = recursive or mrecursive + rep = '%s(%s)' % (object.__class__.__name__, ', '.join(components)) + del context[objid] + return rep, readable, recursive + +_safe_repr_dispatch[_collections.ChainMap.__repr__] = _safe_repr_chain_map + +def _safe_repr_deque(object, context, maxlevels, level): + prefix = object.__class__.__name__ + '([' + if object.maxlen is None: + suffix = '])' + else: + suffix = '], maxlen=%d)' % object.maxlen + return _safe_repr_items(object, object, prefix, suffix, context, maxlevels, level) + +_safe_repr_dispatch[_collections.deque.__repr__] = _safe_repr_deque + +def _safe_repr_user_dict(object, context, maxlevels, level): + 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 + result = _safe_repr(object.data, context, maxlevels, level + 1) + del context[objid] + return result + +_safe_repr_dispatch[_collections.UserDict.__repr__] = _safe_repr_user_dict + +def _safe_repr_user_list(object, context, maxlevels, level): + 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 + result = _safe_repr(object.data, context, maxlevels, level + 1) + del context[objid] + return result + +_safe_repr_dispatch[_collections.UserList.__repr__] = _safe_repr_user_list + +def _safe_repr_items(object, items, prefix, suffix, context, maxlevels, level): + objid = id(object) + if maxlevels and level >= maxlevels: + return ''.join((prefix, '...', suffix)), 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 items: + orepr, oreadable, orecur = _safe_repr(o, context, maxlevels, level) + append(orepr) + if not oreadable: + readable = False + if orecur: + recursive = True + del context[objid] + return ''.join((prefix, ', '.join(components), suffix)), readable, recursive + +def _safe_repr_dict_items(object, items, prefix, suffix, context, maxlevels, level): + objid = id(object) + if maxlevels and level >= maxlevels: + return ''.join((prefix, '...', suffix)), 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 + saferepr = _safe_repr + for k, v in items: + krepr, kreadable, krecur = saferepr(k, context, maxlevels, level) + vrepr, vreadable, vrecur = saferepr(v, context, maxlevels, level) + append('%s: %s' % (krepr, vrepr)) + readable = readable and kreadable and vreadable + if krecur or vrecur: + recursive = True + del context[objid] + return ''.join((prefix, ', '.join(components), suffix)), readable, recursive + _builtin_scalars = frozenset({str, bytes, bytearray, int, float, complex, bool, type(None)}) diff -r e72aab080165 Lib/test/test_pprint.py --- a/Lib/test/test_pprint.py Sat Jul 23 11:16:56 2016 -0400 +++ b/Lib/test/test_pprint.py Sun Jul 24 17:15:41 2016 +0300 @@ -69,6 +69,11 @@ class Orderable: def __hash__(self): return self._hash +class HashableList(list): + def __hash__(self): + return 0 + + class QueryTestCase(unittest.TestCase): def setUp(self): @@ -282,6 +287,7 @@ class QueryTestCase(unittest.TestCase): # 'a', 'c', 'b' here, so this test failed. d = {'a': 1, 'b': 1, 'c': 1} self.assertEqual(pprint.pformat(d), "{'a': 1, 'b': 1, 'c': 1}") + self.assertEqual(pprint.saferepr(d), "{'a': 1, 'b': 1, 'c': 1}") self.assertEqual(pprint.pformat([d, d]), "[{'a': 1, 'b': 1, 'c': 1}, {'a': 1, 'b': 1, 'c': 1}]") @@ -292,12 +298,16 @@ class QueryTestCase(unittest.TestCase): # against a crazy mix of types. self.assertEqual(pprint.pformat({"xy\tab\n": (3,), 5: [[]], (): {}}), r"{5: [[]], 'xy\tab\n': (3,), (): {}}") + self.assertEqual(pprint.saferepr({"xy\tab\n": (3,), 5: [[]], (): {}}), + r"{5: [[]], 'xy\tab\n': (3,), (): {}}") def test_ordered_dict(self): d = collections.OrderedDict() self.assertEqual(pprint.pformat(d, width=1), 'OrderedDict()') + self.assertEqual(pprint.saferepr(d), repr(d)) d = collections.OrderedDict([]) self.assertEqual(pprint.pformat(d, width=1), 'OrderedDict()') + self.assertEqual(pprint.saferepr(d), repr(d)) words = 'the quick brown fox jumped over a lazy dog'.split() d = collections.OrderedDict(zip(words, itertools.count())) self.assertEqual(pprint.pformat(d), @@ -311,6 +321,12 @@ OrderedDict([('the', 0), ('a', 6), ('lazy', 7), ('dog', 8)])""") + self.assertEqual(pprint.saferepr(d), repr(d)) + self.assertTrue(pprint.isreadable(d)) + + d = collections.OrderedDict([('unreadable', int)]) + self.assertEqual(pprint.saferepr(d), repr(d)) + self.assertFalse(pprint.isreadable(d)) def test_mapping_proxy(self): words = 'the quick brown fox jumped over a lazy dog'.split() @@ -326,6 +342,9 @@ mappingproxy({'a': 6, 'over': 5, 'quick': 1, 'the': 0})""") + self.assertEqual(pprint.saferepr(m), + "mappingproxy({'a': 6, 'brown': 2, 'dog': 8, 'fox': 3, " + "'jumped': 4, 'lazy': 7, 'over': 5, 'quick': 1, 'the': 0})") d = collections.OrderedDict(zip(words, itertools.count())) m = types.MappingProxyType(d) self.assertEqual(pprint.pformat(m), """\ @@ -338,6 +357,11 @@ mappingproxy(OrderedDict([('the', 0), ('a', 6), ('lazy', 7), ('dog', 8)]))""") + self.assertEqual(pprint.saferepr(m), repr(m)) + + m = types.MappingProxyType({'unreadable': int}) + self.assertEqual(pprint.saferepr(m), repr(m)) + self.assertFalse(pprint.isreadable(m)) def test_subclassing(self): o = {'names with spaces': 'should be presented using repr()', @@ -349,47 +373,42 @@ mappingproxy(OrderedDict([('the', 0), def test_set_reprs(self): self.assertEqual(pprint.pformat(set()), 'set()') - self.assertEqual(pprint.pformat(set(range(3))), '{0, 1, 2}') - self.assertEqual(pprint.pformat(set(range(7)), width=20), '''\ -{0, - 1, - 2, - 3, - 4, - 5, - 6}''') - self.assertEqual(pprint.pformat(set2(range(7)), width=20), '''\ -set2({0, - 1, - 2, - 3, - 4, - 5, - 6})''') - self.assertEqual(pprint.pformat(set3(range(7)), width=20), - 'set3({0, 1, 2, 3, 4, 5, 6})') + self.assertEqual(pprint.pformat(set('abc')), "{'a', 'b', 'c'}") + self.assertEqual(pprint.saferepr(set('abc')), "{'a', 'b', 'c'}") + self.assertEqual(pprint.pformat(set('abcde'), width=24), '''\ +{'a', + 'b', + 'c', + 'd', + 'e'}''') + self.assertEqual(pprint.pformat(set2('abcde'), width=30), '''\ +set2({'a', + 'b', + 'c', + 'd', + 'e'})''') + s = set3('abcde') + self.assertEqual(pprint.pformat(s, width=24), repr(s)) self.assertEqual(pprint.pformat(frozenset()), 'frozenset()') - self.assertEqual(pprint.pformat(frozenset(range(3))), - 'frozenset({0, 1, 2})') - self.assertEqual(pprint.pformat(frozenset(range(7)), width=20), '''\ -frozenset({0, - 1, - 2, - 3, - 4, - 5, - 6})''') - self.assertEqual(pprint.pformat(frozenset2(range(7)), width=20), '''\ -frozenset2({0, - 1, - 2, - 3, - 4, - 5, - 6})''') - self.assertEqual(pprint.pformat(frozenset3(range(7)), width=20), - 'frozenset3({0, 1, 2, 3, 4, 5, 6})') + self.assertEqual(pprint.pformat(frozenset('abc')), + "frozenset({'a', 'b', 'c'})") + self.assertEqual(pprint.saferepr(frozenset('abc')), + "frozenset({'a', 'b', 'c'})") + self.assertEqual(pprint.pformat(frozenset('abcde'), width=35), '''\ +frozenset({'a', + 'b', + 'c', + 'd', + 'e'})''') + self.assertEqual(pprint.pformat(frozenset2('abcde'), width=36), '''\ +frozenset2({'a', + 'b', + 'c', + 'd', + 'e'})''') + s = frozenset3('abcde') + self.assertEqual(pprint.pformat(s, width=35), repr(s)) @unittest.expectedFailure #See http://bugs.python.org/issue13907 @@ -849,6 +868,7 @@ bytearray(b'\\x00\\x01\\x02\\x03' def test_default_dict(self): d = collections.defaultdict(int) self.assertEqual(pprint.pformat(d, width=1), "defaultdict(, {})") + self.assertEqual(pprint.saferepr(d), repr(d)) words = 'the quick brown fox jumped over a lazy dog'.split() d = collections.defaultdict(int, zip(words, itertools.count())) self.assertEqual(pprint.pformat(d), @@ -863,10 +883,16 @@ defaultdict(, 'over': 5, 'quick': 1, 'the': 0})""") + self.assertEqual(pprint.saferepr(d), + "defaultdict(, {'a': 6, 'brown': 2, " + "'dog': 8, 'fox': 3, 'jumped': 4, 'lazy': 7, 'over': 5, " + "'quick': 1, 'the': 0})") + self.assertFalse(pprint.isreadable(d)) def test_counter(self): d = collections.Counter() self.assertEqual(pprint.pformat(d, width=1), "Counter()") + self.assertEqual(pprint.saferepr(d), repr(d)) d = collections.Counter('senselessness') self.assertEqual(pprint.pformat(d, width=40), """\ @@ -874,10 +900,15 @@ Counter({'s': 6, 'e': 4, 'n': 2, 'l': 1})""") + self.assertEqual(pprint.saferepr(d), repr(d)) + self.assertTrue(pprint.isreadable(d)) + d[int] = 0 + self.assertFalse(pprint.isreadable(d)) def test_chainmap(self): d = collections.ChainMap() self.assertEqual(pprint.pformat(d, width=1), "ChainMap({})") + self.assertEqual(pprint.saferepr(d), repr(d)) words = 'the quick brown fox jumped over a lazy dog'.split() items = list(zip(words, itertools.count())) d = collections.ChainMap(dict(items)) @@ -892,6 +923,13 @@ ChainMap({'a': 6, 'over': 5, 'quick': 1, 'the': 0})""") + self.assertEqual(pprint.saferepr(d), + "ChainMap({'a': 6, 'brown': 2, 'dog': 8, 'fox': 3, " + "'jumped': 4, 'lazy': 7, 'over': 5, 'quick': 1, 'the': 0})") + self.assertTrue(pprint.isreadable(d)) + d = collections.ChainMap({'unreadable': int}) + self.assertFalse(pprint.isreadable(d)) + d = collections.ChainMap(dict(items), collections.OrderedDict(items)) self.assertEqual(pprint.pformat(d), """\ @@ -917,8 +955,10 @@ ChainMap({'a': 6, def test_deque(self): d = collections.deque() self.assertEqual(pprint.pformat(d, width=1), "deque([])") + self.assertEqual(pprint.saferepr(d), repr(d)) d = collections.deque(maxlen=7) self.assertEqual(pprint.pformat(d, width=1), "deque([], maxlen=7)") + self.assertEqual(pprint.saferepr(d), repr(d)) words = 'the quick brown fox jumped over a lazy dog'.split() d = collections.deque(zip(words, itertools.count())) self.assertEqual(pprint.pformat(d), @@ -932,6 +972,8 @@ deque([('the', 0), ('a', 6), ('lazy', 7), ('dog', 8)])""") + self.assertEqual(pprint.saferepr(d), repr(d)) + self.assertTrue(pprint.isreadable(d)) d = collections.deque(zip(words, itertools.count()), maxlen=7) self.assertEqual(pprint.pformat(d), """\ @@ -943,10 +985,17 @@ deque([('brown', 2), ('lazy', 7), ('dog', 8)], maxlen=7)""") + self.assertEqual(pprint.saferepr(d), repr(d)) + self.assertTrue(pprint.isreadable(d)) + + d = collections.deque([int]) + self.assertEqual(pprint.saferepr(d), repr(d)) + self.assertFalse(pprint.isreadable(d)) def test_user_dict(self): d = collections.UserDict() self.assertEqual(pprint.pformat(d, width=1), "{}") + self.assertEqual(pprint.saferepr(d), repr(d)) words = 'the quick brown fox jumped over a lazy dog'.split() d = collections.UserDict(zip(words, itertools.count())) self.assertEqual(pprint.pformat(d), @@ -960,6 +1009,14 @@ deque([('brown', 2), 'over': 5, 'quick': 1, 'the': 0}""") + self.assertEqual(pprint.saferepr(d), + "{'a': 6, 'brown': 2, 'dog': 8, 'fox': 3, 'jumped': 4, " + "'lazy': 7, 'over': 5, 'quick': 1, 'the': 0}") + self.assertTrue(pprint.isreadable(d)) + + d = collections.UserDict({'unreadable': int}) + self.assertEqual(pprint.saferepr(d), repr(d)) + self.assertFalse(pprint.isreadable(d)) def test_user_list(self): d = collections.UserList() @@ -977,6 +1034,12 @@ deque([('brown', 2), ('a', 6), ('lazy', 7), ('dog', 8)]""") + self.assertEqual(pprint.saferepr(d), repr(d)) + self.assertTrue(pprint.isreadable(d)) + + d = collections.UserList([int]) + self.assertEqual(pprint.saferepr(d), repr(d)) + self.assertFalse(pprint.isreadable(d)) def test_user_string(self): d = collections.UserString('') @@ -995,6 +1058,125 @@ deque([('brown', 2), 'lazy dog'}""") +class RecursionTestCase(unittest.TestCase): + def check(self, d, expected, *, depth=2): + self.assertEqual(pprint.pformat(d, width=100, depth=depth), expected) + self.assertEqual(pprint.saferepr(d), expected) + self.assertFalse(pprint.isreadable(d)) + self.assertTrue(pprint.isrecursive(d)) + + def test_dict(self): + d = {} + d['self'] = d + self.check(d, "{'self': }" % id(d)) + self.assertEqual(pprint.pformat(d, depth=1), "{'self': {...}}") + + def test_list(self): + d = [] + d.append(d) + self.check(d, '[]' % id(d)) + self.assertEqual(pprint.pformat(d, depth=1), '[[...]]') + + def test_tuple(self): + d = ([],) + d[0].append(d) + self.check(d, '([],)' % id(d), depth=3) + self.assertEqual(pprint.pformat(d, depth=2), '([(...,)],)') + self.assertEqual(pprint.pformat(d, depth=1), '([...],)') + + def test_set(self): + d = set() + d.add(HashableList([d])) + self.check(d, '{[]}' % id(d), depth=3) + self.assertEqual(pprint.pformat(d, depth=2), '{[{...}]}') + self.assertEqual(pprint.pformat(d, depth=1), '{[...]}') + + def test_frozenset(self): + x = HashableList() + d = frozenset([x]) + x.append(d) + self.check(d, 'frozenset({[]})' % id(d), + depth=3) + self.assertEqual(pprint.pformat(d, depth=2), + 'frozenset({[frozenset({...})]})') + self.assertEqual(pprint.pformat(d, depth=1), 'frozenset({[...]})') + + def test_ordered_dict(self): + d = collections.OrderedDict() + d['self'] = d + self.check(d, "OrderedDict([('self', )])" % id(d), + depth=3) + self.assertEqual(pprint.pformat(d, depth=2), + "OrderedDict([('self', OrderedDict([...]))])") + self.assertEqual(pprint.pformat(d, depth=1), 'OrderedDict([(...)])') + + def test_mapping_proxy(self): + d = {} + m = types.MappingProxyType(d) + d['self'] = m + self.check(m, "mappingproxy({'self': })" % id(m), + depth=3) + self.assertEqual(pprint.pformat(m, depth=2), + "mappingproxy({'self': mappingproxy(...)})") + self.assertEqual(pprint.pformat(m, depth=1), 'mappingproxy({...})') + + def test_default_dict(self): + d = collections.defaultdict(int) + d['self'] = d + self.check(d, + "defaultdict(, {'self': " + "})" % id(d), + depth=3) + self.assertEqual(pprint.pformat(d, depth=2), + "defaultdict(, {'self': defaultdict(...)})") + self.assertEqual(pprint.pformat(d, depth=1), + "defaultdict(, {...})") + + def test_counter(self): + d = collections.Counter() + d['self'] = d + self.check(d, "Counter({'self': })" % id(d)) + self.assertEqual(pprint.pformat(d, depth=1), + "Counter({'self': Counter({...})})") + + def test_chainmap(self): + subd = {} + d = collections.ChainMap(subd) + subd['self'] = d + self.check(d, "ChainMap({'self': })" % id(d), + depth=3) + self.assertEqual(pprint.pformat(d, depth=2), + "ChainMap({'self': ChainMap(...)})") + self.assertEqual(pprint.pformat(d, depth=1), + 'ChainMap({...})') + + def test_deque(self): + d = collections.deque() + d.append(d) + self.check(d, 'deque([])' % id(d)) + self.assertEqual(pprint.pformat(d, depth=1), 'deque([deque([...])])') + d = collections.deque(maxlen=7) + d.append(d) + self.check(d, 'deque([], maxlen=7)' % id(d)) + self.assertEqual(pprint.pformat(d, depth=1), + 'deque([deque([...], maxlen=7)], maxlen=7)') + + def test_user_dict(self): + d = collections.UserDict() + d['self'] = d + self.check(d, "{'self': }" % id(d), + depth=3) + self.assertEqual(pprint.pformat(d, depth=2), "{'self': {...}}") + self.assertEqual(pprint.pformat(d, depth=1), '{...}') + + def test_user_list(self): + d = collections.UserList() + d.append(d) + self.check(d, '[]' % id(d), depth=3) + self.assertEqual(pprint.pformat(d, depth=2), '[[...]]') + self.assertEqual(pprint.pformat(d, depth=1), '[...]') + + class DottedPrettyPrinter(pprint.PrettyPrinter): def format(self, object, context, maxlevels, level):