Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bpo-40890: Add mapping property to dict views #20749

Merged
merged 5 commits into from
Jun 12, 2020
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 12 additions & 0 deletions Doc/library/stdtypes.rst
Expand Up @@ -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,
Expand Down Expand Up @@ -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:

Expand Down
20 changes: 20 additions & 0 deletions Lib/test/test_dict.py
Expand Up @@ -105,6 +105,26 @@ def test_items(self):
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)
Expand Down
@@ -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.
23 changes: 20 additions & 3 deletions Objects/dictobject.c
Expand Up @@ -4122,6 +4122,23 @@ _PyDictView_New(PyObject *dict, PyTypeObject *type)
return (PyObject *)dv;
}

static PyObject *
dictview_mapping(PyObject *view)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should have signature compatible with getter:

static PyObject *
dictview_mapping(PyObject *view, void *Py_UNUSED(ignored))

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in #20876

{
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,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Explicit cast to getter is not needed if fix the signature.

"dictionary that this view refers to", NULL},
{0}
};

/* TODO(guido): The views objects are not complete:

* support more set operations
Expand Down Expand Up @@ -4545,7 +4562,7 @@ PyTypeObject PyDictKeys_Type = {
(getiterfunc)dictkeys_iter, /* tp_iter */
0, /* tp_iternext */
dictkeys_methods, /* tp_methods */
0,
.tp_getset = dictview_getset,
};

static PyObject *
Expand Down Expand Up @@ -4651,7 +4668,7 @@ PyTypeObject PyDictItems_Type = {
(getiterfunc)dictitems_iter, /* tp_iter */
0, /* tp_iternext */
dictitems_methods, /* tp_methods */
0,
.tp_getset = dictview_getset,
};

static PyObject *
Expand Down Expand Up @@ -4732,7 +4749,7 @@ PyTypeObject PyDictValues_Type = {
(getiterfunc)dictvalues_iter, /* tp_iter */
0, /* tp_iternext */
dictvalues_methods, /* tp_methods */
0,
.tp_getset = dictview_getset,
};

static PyObject *
Expand Down