https://github.com/python/cpython/commit/c7f8e706e116d7986da40805cc4a85333acde1c9
commit: c7f8e706e116d7986da40805cc4a85333acde1c9
branch: main
author: devdanzin <74280297+devdan...@users.noreply.github.com>
committer: gpshead <g...@krypto.org>
date: 2025-05-20T12:30:00-07:00
summary:

gh-90117: handle dict and mapping views in pprint (#30135)

* Teach pprint about dict views with PrettyPrinter._pprint_dict_view and 
._pprint_dict_items_view.
* Use _private names for _dict_*_view attributes of PrettyPrinter.
* Use explicit 'items' keyword when calling _pprint_dict_view from 
_pprint_dict_items_view.
* ๐Ÿ“œ๐Ÿค– Added by blurb_it.
* Improve tests
* Add tests for collections.abc.[Keys|Items|Mapping|Values]View support in 
pprint.
* Add support for collections.abc.[Keys|Items|Mapping|Values]View in pprint.
* Split _pprint_dict_view into _pprint_abc_view, so pretty-printing normal dict 
views and ABC views is handled in two simple methods.
* Simplify redundant code.
* Add collections.abc views to some existing pprint tests.
* Test that views from collection.UserDict are correctly formatted by pprint.
* Handle recursive dict and ABC views.
* Test that subclasses of ABC views work in pprint.
* Test dict views coming from collections.Counter.
* Test ABC views coming from collections.ChainMap.
* Test odict views coming from collections.OrderedDict.
* Rename _pprint_abc_view to _pprint_mapping_abc_view.
* Add pprint test for mapping ABC views where ._mapping has a custom __repr__ 
and fix ChainMap test.
* When a mapping ABC view has a ._mapping that defines a custom __repr__, 
dispatch pretty-printing it by that __repr__.
* Add tests for ABC mapping views subclasses that don't replace __repr__, also 
handling those that delete ._mapping on instances.
* Simplify the pretty printing of ABC mapping views.
* Add a test for depth handling when pretty printing dict views.
* Fix checking whether the view type is a subclass of an items view, add a test.
* Move construction of the views __repr__ set out of _safe_repr.

---------

Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com>
Co-authored-by: ร‰ric <mer...@netwok.org>
Co-authored-by: Oleg Iarygin <o...@arhadthedev.net>
Co-authored-by: Adam Turner <9087854+aa-tur...@users.noreply.github.com>
Co-authored-by: Gregory P. Smith <g...@krypto.org>

files:
A Misc/NEWS.d/next/Library/2021-12-18-12-46-20.bpo-45959.vPlr3P.rst
M Lib/pprint.py
M Lib/test/test_pprint.py

diff --git a/Lib/pprint.py b/Lib/pprint.py
index dc0953cec67a58..1e611481b51ac0 100644
--- a/Lib/pprint.py
+++ b/Lib/pprint.py
@@ -248,6 +248,49 @@ def _pprint_ordered_dict(self, object, stream, indent, 
allowance, context, level
 
     _dispatch[_collections.OrderedDict.__repr__] = _pprint_ordered_dict
 
+    def _pprint_dict_view(self, object, stream, indent, allowance, context, 
level):
+        """Pretty print dict views (keys, values, items)."""
+        if isinstance(object, self._dict_items_view):
+            key = _safe_tuple
+        else:
+            key = _safe_key
+        write = stream.write
+        write(object.__class__.__name__ + '([')
+        if self._indent_per_level > 1:
+            write((self._indent_per_level - 1) * ' ')
+        length = len(object)
+        if length:
+            if self._sort_dicts:
+                entries = sorted(object, key=key)
+            else:
+                entries = object
+            self._format_items(entries, stream, indent, allowance + 1,
+                               context, level)
+        write('])')
+
+    def _pprint_mapping_abc_view(self, object, stream, indent, allowance, 
context, level):
+        """Pretty print mapping views from collections.abc."""
+        write = stream.write
+        write(object.__class__.__name__ + '(')
+        # Dispatch formatting to the view's _mapping
+        self._format(object._mapping, stream, indent, allowance, context, 
level)
+        write(')')
+
+    _dict_keys_view = type({}.keys())
+    _dispatch[_dict_keys_view.__repr__] = _pprint_dict_view
+
+    _dict_values_view = type({}.values())
+    _dispatch[_dict_values_view.__repr__] = _pprint_dict_view
+
+    _dict_items_view = type({}.items())
+    _dispatch[_dict_items_view.__repr__] = _pprint_dict_view
+
+    _dispatch[_collections.abc.MappingView.__repr__] = _pprint_mapping_abc_view
+
+    _view_reprs = {cls.__repr__ for cls in
+                   (_dict_keys_view, _dict_values_view, _dict_items_view,
+                    _collections.abc.MappingView)}
+
     def _pprint_list(self, object, stream, indent, allowance, context, level):
         stream.write('[')
         self._format_items(object, stream, indent, allowance + 1,
@@ -610,6 +653,42 @@ def _safe_repr(self, object, context, maxlevels, level):
             del context[objid]
             return "{%s}" % ", ".join(components), readable, recursive
 
+        if issubclass(typ, _collections.abc.MappingView) and r in 
self._view_reprs:
+            objid = id(object)
+            if maxlevels and level >= maxlevels:
+                return "{...}", False, objid in context
+            if objid in context:
+                return _recursion(object), False, True
+            key = _safe_key
+            if issubclass(typ, (self._dict_items_view, 
_collections.abc.ItemsView)):
+                key = _safe_tuple
+            if hasattr(object, "_mapping"):
+                # Dispatch formatting to the view's _mapping
+                mapping_repr, readable, recursive = self.format(
+                    object._mapping, context, maxlevels, level)
+                return (typ.__name__ + '(%s)' % mapping_repr), readable, 
recursive
+            elif hasattr(typ, "_mapping"):
+                #  We have a view that somehow has lost its type's _mapping, 
raise
+                #  an error by calling repr() instead of failing cryptically 
later
+                return repr(object), True, False
+            if self._sort_dicts:
+                object = sorted(object, key=key)
+            context[objid] = 1
+            readable = True
+            recursive = False
+            components = []
+            append = components.append
+            level += 1
+            for val in object:
+                vrepr, vreadable, vrecur = self.format(
+                    val, context, maxlevels, level)
+                append(vrepr)
+                readable = readable and vreadable
+                if vrecur:
+                    recursive = True
+            del context[objid]
+            return typ.__name__ + '([%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):
diff --git a/Lib/test/test_pprint.py b/Lib/test/test_pprint.py
index f68996f72b15b5..0c84d3d3bfd17a 100644
--- a/Lib/test/test_pprint.py
+++ b/Lib/test/test_pprint.py
@@ -10,6 +10,7 @@
 import re
 import types
 import unittest
+from collections.abc import ItemsView, KeysView, Mapping, MappingView, 
ValuesView
 
 from test.support import cpython_only
 from test.support.import_helper import ensure_lazy_imports
@@ -70,6 +71,14 @@ class dict_custom_repr(dict):
     def __repr__(self):
         return '*'*len(dict.__repr__(self))
 
+class mappingview_custom_repr(MappingView):
+    def __repr__(self):
+        return '*'*len(MappingView.__repr__(self))
+
+class keysview_custom_repr(KeysView):
+    def __repr__(self):
+        return '*'*len(KeysView.__repr__(self))
+
 @dataclasses.dataclass
 class dataclass1:
     field1: str
@@ -180,10 +189,17 @@ def test_knotted(self):
         # Messy dict.
         self.d = {}
         self.d[0] = self.d[1] = self.d[2] = self.d
+        self.e = {}
+        self.v = ValuesView(self.e)
+        self.m = MappingView(self.e)
+        self.dv = self.e.values()
+        self.e["v"] = self.v
+        self.e["m"] = self.m
+        self.e["dv"] = self.dv
 
         pp = pprint.PrettyPrinter()
 
-        for icky in self.a, self.b, self.d, (self.d, self.d):
+        for icky in self.a, self.b, self.d, (self.d, self.d), self.e, self.v, 
self.m, self.dv:
             self.assertTrue(pprint.isrecursive(icky), "expected isrecursive")
             self.assertFalse(pprint.isreadable(icky), "expected not 
isreadable")
             self.assertTrue(pp.isrecursive(icky), "expected isrecursive")
@@ -191,10 +207,11 @@ def test_knotted(self):
 
         # Break the cycles.
         self.d.clear()
+        self.e.clear()
         del self.a[:]
         del self.b[:]
 
-        for safe in self.a, self.b, self.d, (self.d, self.d):
+        for safe in self.a, self.b, self.d, (self.d, self.d), self.e, self.v, 
self.m, self.dv:
             # module-level convenience functions
             self.assertFalse(pprint.isrecursive(safe),
                              "expected not isrecursive for %r" % (safe,))
@@ -237,6 +254,8 @@ def test_same_as_repr(self):
                        set(), set2(), set3(),
                        frozenset(), frozenset2(), frozenset3(),
                        {}, dict2(), dict3(),
+                       {}.keys(), {}.values(), {}.items(),
+                       MappingView({}), KeysView({}), ItemsView({}), 
ValuesView({}),
                        self.assertTrue, pprint,
                        -6, -6, -6-6j, -1.5, "x", b"x", bytearray(b"x"),
                        (3,), [3], {3: 6},
@@ -246,6 +265,9 @@ def test_same_as_repr(self):
                        set({7}), set2({7}), set3({7}),
                        frozenset({8}), frozenset2({8}), frozenset3({8}),
                        dict2({5: 6}), dict3({5: 6}),
+                       {5: 6}.keys(), {5: 6}.values(), {5: 6}.items(),
+                       MappingView({5: 6}), KeysView({5: 6}),
+                       ItemsView({5: 6}), ValuesView({5: 6}),
                        range(10, -11, -1),
                        True, False, None, ...,
                       ):
@@ -275,6 +297,12 @@ def test_container_repr_override_called(self):
                      dict_custom_repr(),
                      dict_custom_repr({5: 6}),
                      dict_custom_repr(zip(range(N),range(N))),
+                     mappingview_custom_repr({}),
+                     mappingview_custom_repr({5: 6}),
+                     mappingview_custom_repr(dict(zip(range(N),range(N)))),
+                     keysview_custom_repr({}),
+                     keysview_custom_repr({5: 6}),
+                     keysview_custom_repr(dict(zip(range(N),range(N)))),
                     ):
             native = repr(cont)
             expected = '*' * len(native)
@@ -302,6 +330,56 @@ def test_basic_line_wrap(self):
         for type in [dict, dict2]:
             self.assertEqual(pprint.pformat(type(o)), exp)
 
+        o = range(100)
+        exp = 'dict_keys([%s])' % ',\n '.join(map(str, o))
+        keys = dict.fromkeys(o).keys()
+        self.assertEqual(pprint.pformat(keys), exp)
+
+        o = range(100)
+        exp = 'dict_values([%s])' % ',\n '.join(map(str, o))
+        values = {v: v for v in o}.values()
+        self.assertEqual(pprint.pformat(values), exp)
+
+        o = range(100)
+        exp = 'dict_items([%s])' % ',\n '.join("(%s, %s)" % (i, i) for i in o)
+        items = {v: v for v in o}.items()
+        self.assertEqual(pprint.pformat(items), exp)
+
+        o = range(100)
+        exp = 'odict_keys([%s])' % ',\n '.join(map(str, o))
+        keys = collections.OrderedDict.fromkeys(o).keys()
+        self.assertEqual(pprint.pformat(keys), exp)
+
+        o = range(100)
+        exp = 'odict_values([%s])' % ',\n '.join(map(str, o))
+        values = collections.OrderedDict({v: v for v in o}).values()
+        self.assertEqual(pprint.pformat(values), exp)
+
+        o = range(100)
+        exp = 'odict_items([%s])' % ',\n '.join("(%s, %s)" % (i, i) for i in o)
+        items = collections.OrderedDict({v: v for v in o}).items()
+        self.assertEqual(pprint.pformat(items), exp)
+
+        o = range(100)
+        exp = 'KeysView({%s})' % (': None,\n '.join(map(str, o)) + ': None')
+        keys_view = KeysView(dict.fromkeys(o))
+        self.assertEqual(pprint.pformat(keys_view), exp)
+
+        o = range(100)
+        exp = 'ItemsView({%s})' % (': None,\n '.join(map(str, o)) + ': None')
+        items_view = ItemsView(dict.fromkeys(o))
+        self.assertEqual(pprint.pformat(items_view), exp)
+
+        o = range(100)
+        exp = 'MappingView({%s})' % (': None,\n '.join(map(str, o)) + ': None')
+        mapping_view = MappingView(dict.fromkeys(o))
+        self.assertEqual(pprint.pformat(mapping_view), exp)
+
+        o = range(100)
+        exp = 'ValuesView({%s})' % (': None,\n '.join(map(str, o)) + ': None')
+        values_view = ValuesView(dict.fromkeys(o))
+        self.assertEqual(pprint.pformat(values_view), exp)
+
         o = range(100)
         exp = '[%s]' % ',\n '.join(map(str, o))
         for type in [list, list2]:
@@ -425,6 +503,30 @@ def test_ordered_dict(self):
              ('a', 6),
              ('lazy', 7),
              ('dog', 8)])""")
+        self.assertEqual(pprint.pformat(d.keys(), sort_dicts=False),
+"""\
+odict_keys(['the',
+ 'quick',
+ 'brown',
+ 'fox',
+ 'jumped',
+ 'over',
+ 'a',
+ 'lazy',
+ 'dog'])""")
+        self.assertEqual(pprint.pformat(d.items(), sort_dicts=False),
+"""\
+odict_items([('the', 0),
+ ('quick', 1),
+ ('brown', 2),
+ ('fox', 3),
+ ('jumped', 4),
+ ('over', 5),
+ ('a', 6),
+ ('lazy', 7),
+ ('dog', 8)])""")
+        self.assertEqual(pprint.pformat(d.values(), sort_dicts=False),
+                         "odict_values([0, 1, 2, 3, 4, 5, 6, 7, 8])")
 
     def test_mapping_proxy(self):
         words = 'the quick brown fox jumped over a lazy dog'.split()
@@ -453,6 +555,152 @@ def test_mapping_proxy(self):
                           ('lazy', 7),
                           ('dog', 8)]))""")
 
+    def test_dict_views(self):
+        for dict_class in (dict, collections.OrderedDict, collections.Counter):
+            empty = dict_class({})
+            short = dict_class(dict(zip('edcba', 'edcba')))
+            long = dict_class(dict((chr(x), chr(x)) for x in range(90, 64, 
-1)))
+            lengths = {"empty": empty, "short": short, "long": long}
+            prefix = "odict" if dict_class is collections.OrderedDict else 
"dict"
+            for name, d in lengths.items():
+                with self.subTest(length=name, prefix=prefix):
+                    is_short = len(d) < 6
+                    joiner = ", " if is_short else ",\n "
+                    k = d.keys()
+                    v = d.values()
+                    i = d.items()
+                    self.assertEqual(pprint.pformat(k, sort_dicts=True),
+                                     prefix + "_keys([%s])" %
+                                     joiner.join(repr(key) for key in 
sorted(k)))
+                    self.assertEqual(pprint.pformat(v, sort_dicts=True),
+                                     prefix + "_values([%s])" %
+                                     joiner.join(repr(val) for val in 
sorted(v)))
+                    self.assertEqual(pprint.pformat(i, sort_dicts=True),
+                                     prefix + "_items([%s])" %
+                                     joiner.join(repr(item) for item in 
sorted(i)))
+                    self.assertEqual(pprint.pformat(k, sort_dicts=False),
+                                     prefix + "_keys([%s])" %
+                                     joiner.join(repr(key) for key in k))
+                    self.assertEqual(pprint.pformat(v, sort_dicts=False),
+                                     prefix + "_values([%s])" %
+                                     joiner.join(repr(val) for val in v))
+                    self.assertEqual(pprint.pformat(i, sort_dicts=False),
+                                     prefix + "_items([%s])" %
+                                     joiner.join(repr(item) for item in i))
+
+    def test_abc_views(self):
+        empty = {}
+        short = dict(zip('edcba', 'edcba'))
+        long = dict((chr(x), chr(x)) for x in range(90, 64, -1))
+        lengths = {"empty": empty, "short": short, "long": long}
+        # Test that a subclass that doesn't replace __repr__ works with 
different lengths
+        class MV(MappingView): pass
+
+        for name, d in lengths.items():
+            with self.subTest(length=name, name="Views"):
+                is_short = len(d) < 6
+                joiner = ", " if is_short else ",\n "
+                i = d.items()
+                s = sorted(i)
+                joined_items = "({%s})" % joiner.join(["%r: %r" % (k, v) for 
(k, v) in i])
+                sorted_items = "({%s})" % joiner.join(["%r: %r" % (k, v) for 
(k, v) in s])
+                self.assertEqual(pprint.pformat(KeysView(d), sort_dicts=True),
+                                 KeysView.__name__ + sorted_items)
+                self.assertEqual(pprint.pformat(ItemsView(d), sort_dicts=True),
+                                 ItemsView.__name__ + sorted_items)
+                self.assertEqual(pprint.pformat(MappingView(d), 
sort_dicts=True),
+                                 MappingView.__name__ + sorted_items)
+                self.assertEqual(pprint.pformat(MV(d), sort_dicts=True),
+                                 MV.__name__ + sorted_items)
+                self.assertEqual(pprint.pformat(ValuesView(d), 
sort_dicts=True),
+                                 ValuesView.__name__ + sorted_items)
+                self.assertEqual(pprint.pformat(KeysView(d), sort_dicts=False),
+                                 KeysView.__name__ + joined_items)
+                self.assertEqual(pprint.pformat(ItemsView(d), 
sort_dicts=False),
+                                 ItemsView.__name__ + joined_items)
+                self.assertEqual(pprint.pformat(MappingView(d), 
sort_dicts=False),
+                                 MappingView.__name__ + joined_items)
+                self.assertEqual(pprint.pformat(MV(d), sort_dicts=False),
+                                 MV.__name__ + joined_items)
+                self.assertEqual(pprint.pformat(ValuesView(d), 
sort_dicts=False),
+                                 ValuesView.__name__ + joined_items)
+
+    def test_nested_views(self):
+        d = {1: MappingView({1: MappingView({1: MappingView({1: 2})})})}
+        self.assertEqual(repr(d),
+                         "{1: MappingView({1: MappingView({1: MappingView({1: 
2})})})}")
+        self.assertEqual(pprint.pformat(d),
+                         "{1: MappingView({1: MappingView({1: MappingView({1: 
2})})})}")
+        self.assertEqual(pprint.pformat(d, depth=2),
+                         "{1: MappingView({1: {...}})}")
+        d = {}
+        d1 = {1: d.values()}
+        d2 = {1: d1.values()}
+        d3 = {1: d2.values()}
+        self.assertEqual(pprint.pformat(d3),
+                         "{1: dict_values([dict_values([dict_values([])])])}")
+        self.assertEqual(pprint.pformat(d3, depth=2),
+                         "{1: dict_values([{...}])}")
+
+    def test_unorderable_items_views(self):
+        """Check that views with unorderable items have stable sorting."""
+        d = dict((((3+1j), 3), ((1+1j), (1+0j)), (1j, 0j), (500, None), (499, 
None)))
+        iv = ItemsView(d)
+        self.assertEqual(pprint.pformat(iv),
+                         pprint.pformat(iv))
+        self.assertTrue(pprint.pformat(iv).endswith(", 499: None, 500: 
None})"),
+                        pprint.pformat(iv))
+        self.assertEqual(pprint.pformat(d.items()),  # Won't be equal unless 
_safe_tuple
+                         pprint.pformat(d.items()))  # is used in _safe_repr
+        self.assertTrue(pprint.pformat(d.items()).endswith(", (499, None), 
(500, None)])"))
+
+    def test_mapping_view_subclass_no_mapping(self):
+        class BMV(MappingView):
+            def __init__(self, d):
+                super().__init__(d)
+                self.mapping = self._mapping
+                del self._mapping
+
+        self.assertRaises(AttributeError, pprint.pformat, BMV({}))
+
+    def test_mapping_subclass_repr(self):
+        """Test that mapping ABC views use their ._mapping's __repr__."""
+        class MyMapping(Mapping):
+            def __init__(self, keys=None):
+                self._keys = {} if keys is None else dict.fromkeys(keys)
+
+            def __getitem__(self, item):
+                return self._keys[item]
+
+            def __len__(self):
+                return len(self._keys)
+
+            def __iter__(self):
+                return iter(self._keys)
+
+            def __repr__(self):
+                return f"{self.__class__.__name__}([{', '.join(map(repr, 
self._keys.keys()))}])"
+
+        m = MyMapping(["test", 1])
+        self.assertEqual(repr(m), "MyMapping(['test', 1])")
+        short_view_repr = "%s(MyMapping(['test', 1]))"
+        self.assertEqual(repr(m.keys()), short_view_repr % "KeysView")
+        self.assertEqual(pprint.pformat(m.items()), short_view_repr % 
"ItemsView")
+        self.assertEqual(pprint.pformat(m.keys()), short_view_repr % 
"KeysView")
+        self.assertEqual(pprint.pformat(MappingView(m)), short_view_repr % 
"MappingView")
+        self.assertEqual(pprint.pformat(m.values()), short_view_repr % 
"ValuesView")
+
+        alpha = "abcdefghijklmnopqrstuvwxyz"
+        m = MyMapping(alpha)
+        alpha_repr = ", ".join(map(repr, list(alpha)))
+        long_view_repr = "%%s(MyMapping([%s]))" % alpha_repr
+        self.assertEqual(repr(m), "MyMapping([%s])" % alpha_repr)
+        self.assertEqual(repr(m.keys()), long_view_repr % "KeysView")
+        self.assertEqual(pprint.pformat(m.items()), long_view_repr % 
"ItemsView")
+        self.assertEqual(pprint.pformat(m.keys()), long_view_repr % "KeysView")
+        self.assertEqual(pprint.pformat(MappingView(m)), long_view_repr % 
"MappingView")
+        self.assertEqual(pprint.pformat(m.values()), long_view_repr % 
"ValuesView")
+
     def test_empty_simple_namespace(self):
         ns = types.SimpleNamespace()
         formatted = pprint.pformat(ns)
@@ -768,6 +1016,10 @@ def test_sort_unorderable_values(self):
             'frozenset({' + ','.join(map(repr, skeys)) + '})')
         self.assertEqual(clean(pprint.pformat(dict.fromkeys(keys))),
             '{' + ','.join('%r:None' % k for k in skeys) + '}')
+        self.assertEqual(clean(pprint.pformat(dict.fromkeys(keys).keys())),
+            'dict_keys([' + ','.join('%r' % k for k in skeys) + '])')
+        self.assertEqual(clean(pprint.pformat(dict.fromkeys(keys).items())),
+            'dict_items([' + ','.join('(%r,None)' % k for k in skeys) + '])')
 
         # Issue 10017: TypeError on user-defined types as dict keys.
         self.assertEqual(pprint.pformat({Unorderable: 0, 1: 0}),
@@ -1049,6 +1301,66 @@ def test_chainmap(self):
                       ('a', 6),
                       ('lazy', 7),
                       ('dog', 8)]))""")
+        self.assertEqual(pprint.pformat(d.keys()),
+"""\
+KeysView(ChainMap({'a': 6,
+          'brown': 2,
+          'dog': 8,
+          'fox': 3,
+          'jumped': 4,
+          'lazy': 7,
+          'over': 5,
+          'quick': 1,
+          'the': 0},
+         OrderedDict([('the', 0),
+                      ('quick', 1),
+                      ('brown', 2),
+                      ('fox', 3),
+                      ('jumped', 4),
+                      ('over', 5),
+                      ('a', 6),
+                      ('lazy', 7),
+                      ('dog', 8)])))""")
+        self.assertEqual(pprint.pformat(d.items()),
+ """\
+ItemsView(ChainMap({'a': 6,
+          'brown': 2,
+          'dog': 8,
+          'fox': 3,
+          'jumped': 4,
+          'lazy': 7,
+          'over': 5,
+          'quick': 1,
+          'the': 0},
+         OrderedDict([('the', 0),
+                      ('quick', 1),
+                      ('brown', 2),
+                      ('fox', 3),
+                      ('jumped', 4),
+                      ('over', 5),
+                      ('a', 6),
+                      ('lazy', 7),
+                      ('dog', 8)])))""")
+        self.assertEqual(pprint.pformat(d.values()),
+ """\
+ValuesView(ChainMap({'a': 6,
+          'brown': 2,
+          'dog': 8,
+          'fox': 3,
+          'jumped': 4,
+          'lazy': 7,
+          'over': 5,
+          'quick': 1,
+          'the': 0},
+         OrderedDict([('the', 0),
+                      ('quick', 1),
+                      ('brown', 2),
+                      ('fox', 3),
+                      ('jumped', 4),
+                      ('over', 5),
+                      ('a', 6),
+                      ('lazy', 7),
+                      ('dog', 8)])))""")
 
     def test_deque(self):
         d = collections.deque()
@@ -1096,6 +1408,36 @@ def test_user_dict(self):
  'over': 5,
  'quick': 1,
  'the': 0}""")
+        self.assertEqual(pprint.pformat(d.keys()), """\
+KeysView({'a': 6,
+ 'brown': 2,
+ 'dog': 8,
+ 'fox': 3,
+ 'jumped': 4,
+ 'lazy': 7,
+ 'over': 5,
+ 'quick': 1,
+ 'the': 0})""")
+        self.assertEqual(pprint.pformat(d.items()), """\
+ItemsView({'a': 6,
+ 'brown': 2,
+ 'dog': 8,
+ 'fox': 3,
+ 'jumped': 4,
+ 'lazy': 7,
+ 'over': 5,
+ 'quick': 1,
+ 'the': 0})""")
+        self.assertEqual(pprint.pformat(d.values()), """\
+ValuesView({'a': 6,
+ 'brown': 2,
+ 'dog': 8,
+ 'fox': 3,
+ 'jumped': 4,
+ 'lazy': 7,
+ 'over': 5,
+ 'quick': 1,
+ 'the': 0})""")
 
     def test_user_list(self):
         d = collections.UserList()
diff --git a/Misc/NEWS.d/next/Library/2021-12-18-12-46-20.bpo-45959.vPlr3P.rst 
b/Misc/NEWS.d/next/Library/2021-12-18-12-46-20.bpo-45959.vPlr3P.rst
new file mode 100644
index 00000000000000..bcafa64d2638dc
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2021-12-18-12-46-20.bpo-45959.vPlr3P.rst
@@ -0,0 +1 @@
+:mod:`pprint` can now pretty-print dict views.

_______________________________________________
Python-checkins mailing list -- python-checkins@python.org
To unsubscribe send an email to python-checkins-le...@python.org
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: arch...@mail-archive.com

Reply via email to