From 3ee0e48b0376a710c08eec6f30e4181563b192a2 Mon Sep 17 00:00:00 2001 From: Dennis Sweeney <36520290+sweeneyde@users.noreply.github.com> Date: Fri, 12 Jun 2020 13:19:25 -0400 Subject: [PATCH] bpo-40890: Add `mapping` property to dict views (GH-20749) --- Doc/library/stdtypes.rst | 12 ++++++++++ Doc/whatsnew/3.10.rst | 5 ++++ Lib/test/test_dict.py | 20 ++++++++++++++++ .../2020-06-09-00-20-13.bpo-40890.LoRV-g.rst | 1 + Objects/dictobject.c | 23 ++++++++++++++++--- 5 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2020-06-09-00-20-13.bpo-40890.LoRV-g.rst diff --git a/Doc/library/stdtypes.rst b/Doc/library/stdtypes.rst index 2082b849fd0..7028d240c59 100644 --- a/Doc/library/stdtypes.rst +++ b/Doc/library/stdtypes.rst @@ -4622,6 +4622,12 @@ support membership tests: .. versionchanged:: 3.8 Dictionary views are now reversible. +.. describe:: dictview.mapping + + Return a :class:`types.MappingProxyType` that wraps the original + dictionary to which the view refers. + + .. versionadded:: 3.10 Keys views are set-like since their entries are unique and hashable. If all values are hashable, so that ``(key, value)`` pairs are unique and hashable, @@ -4661,6 +4667,12 @@ An example of dictionary view usage:: >>> keys ^ {'sausage', 'juice'} {'juice', 'sausage', 'bacon', 'spam'} + >>> # get back a read-only proxy for the original dictionary + >>> values.mapping + mappingproxy({'eggs': 2, 'sausage': 1, 'bacon': 1, 'spam': 500}) + >>> values.mapping['spam'] + 500 + .. _typecontextmanager: diff --git a/Doc/whatsnew/3.10.rst b/Doc/whatsnew/3.10.rst index 1234b2e6bbf..629909b79e2 100644 --- a/Doc/whatsnew/3.10.rst +++ b/Doc/whatsnew/3.10.rst @@ -74,6 +74,11 @@ New Features number of ones in the binary expansion of a given integer, also known as the population count. (Contributed by Niklas Fiekas in :issue:`29882`.) +* The views returned by :meth:`dict.keys`, :meth:`dict.values` and + :meth:`dict.items` now all have a ``mapping`` attribute that gives a + :class:`types.MappingProxyType` object wrapping the original + dictionary. (Contributed by Dennis Sweeney in :issue:`40890`.) + Other Language Changes ====================== diff --git a/Lib/test/test_dict.py b/Lib/test/test_dict.py index 5c08810f879..9ff8b7d501a 100644 --- a/Lib/test/test_dict.py +++ b/Lib/test/test_dict.py @@ -105,6 +105,26 @@ class DictTest(unittest.TestCase): self.assertRaises(TypeError, d.items, None) self.assertEqual(repr(dict(a=1).items()), "dict_items([('a', 1)])") + def test_views_mapping(self): + mappingproxy = type(type.__dict__) + class Dict(dict): + pass + for cls in [dict, Dict]: + d = cls() + m1 = d.keys().mapping + m2 = d.values().mapping + m3 = d.items().mapping + + for m in [m1, m2, m3]: + self.assertIsInstance(m, mappingproxy) + self.assertEqual(m, d) + + d["foo"] = "bar" + + for m in [m1, m2, m3]: + self.assertIsInstance(m, mappingproxy) + self.assertEqual(m, d) + def test_contains(self): d = {} self.assertNotIn('a', d) diff --git a/Misc/NEWS.d/next/Core and Builtins/2020-06-09-00-20-13.bpo-40890.LoRV-g.rst b/Misc/NEWS.d/next/Core and Builtins/2020-06-09-00-20-13.bpo-40890.LoRV-g.rst new file mode 100644 index 00000000000..eaefc894a13 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2020-06-09-00-20-13.bpo-40890.LoRV-g.rst @@ -0,0 +1 @@ +Each dictionary view now has a ``mapping`` attribute that provides a :class:`types.MappingProxyType` wrapping the original dictionary. Patch contributed by Dennis Sweeney. \ No newline at end of file diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 1bb8cfdab2b..48e96a09a5f 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -4122,6 +4122,23 @@ _PyDictView_New(PyObject *dict, PyTypeObject *type) return (PyObject *)dv; } +static PyObject * +dictview_mapping(PyObject *view) +{ + assert(view != NULL); + assert(PyDictKeys_Check(view) + || PyDictValues_Check(view) + || PyDictItems_Check(view)); + PyObject *mapping = (PyObject *)((_PyDictViewObject *)view)->dv_dict; + return PyDictProxy_New(mapping); +} + +static PyGetSetDef dictview_getset[] = { + {"mapping", (getter)dictview_mapping, (setter)NULL, + "dictionary that this view refers to", NULL}, + {0} +}; + /* TODO(guido): The views objects are not complete: * support more set operations @@ -4635,7 +4652,7 @@ PyTypeObject PyDictKeys_Type = { (getiterfunc)dictkeys_iter, /* tp_iter */ 0, /* tp_iternext */ dictkeys_methods, /* tp_methods */ - 0, + .tp_getset = dictview_getset, }; static PyObject * @@ -4741,7 +4758,7 @@ PyTypeObject PyDictItems_Type = { (getiterfunc)dictitems_iter, /* tp_iter */ 0, /* tp_iternext */ dictitems_methods, /* tp_methods */ - 0, + .tp_getset = dictview_getset, }; static PyObject * @@ -4822,7 +4839,7 @@ PyTypeObject PyDictValues_Type = { (getiterfunc)dictvalues_iter, /* tp_iter */ 0, /* tp_iternext */ dictvalues_methods, /* tp_methods */ - 0, + .tp_getset = dictview_getset, }; static PyObject *