From 34ab6de70a39d08f5f7eb7c9155f7bde28cbed0e Mon Sep 17 00:00:00 2001 From: Brandt Bucher Date: Tue, 25 Feb 2020 09:36:34 -0800 Subject: [PATCH 1/9] Add failing MappingProxy tests. --- Lib/test/test_types.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index 7b45b7a5895039..6828945c51bdbd 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -622,8 +622,11 @@ def test_methods(self): self.assertEqual(attrs, { '__contains__', '__getitem__', + '__ior__', '__iter__', '__len__', + '__or__', + '__ror__', 'copy', 'get', 'items', @@ -774,6 +777,26 @@ def test_copy(self): self.assertEqual(view['key1'], 70) self.assertEqual(copy['key1'], 27) + def test_union(self): + mapping = {'a': 0, 'b': 1, 'c': 2} + view = self.mappingproxy(mapping) + with self.assertRaises(TypeError): + view | [('r', 2), ('d', 2)] + with self.assertRaises(TypeError): + [('r', 2), ('d', 2)] | view + with self.assertRaises(TypeError): + view |= None + other = {'c': 3, 'p': 0} + self.assertDictEqual(view | other, {'a': 0, 'b': 1, 'c': 3, 'p': 0}) + self.assertDictEqual(other | view, {'c': 2, 'p': 0, 'a': 0, 'b': 1}) + view |= other + self.assertDictEqual(view, {'a': 0, 'b': 1, 'c': 3, 'p': 0}) + view = self.mappingproxy(mapping) + view |= iter((('c', 3), ('p', 0))) + self.assertDictEqual(view, {'a': 0, 'b': 1, 'c': 3, 'p': 0}) + self.assertDictEqual(mapping, {'a': 0, 'b': 1, 'c': 2}) + self.assertDictEqual(other, {'c': 3, 'p': 0}) + class ClassCreationTests(unittest.TestCase): From a3178d0cd45a3281a7c7f9acfb0e3778c7df43a2 Mon Sep 17 00:00:00 2001 From: Brandt Bucher Date: Tue, 25 Feb 2020 09:37:02 -0800 Subject: [PATCH 2/9] Implement MappingProxy | and |=. --- Objects/descrobject.c | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/Objects/descrobject.c b/Objects/descrobject.c index aaaa4479e4b923..86f95d1d42cf06 100644 --- a/Objects/descrobject.c +++ b/Objects/descrobject.c @@ -982,6 +982,33 @@ static PyMappingMethods mappingproxy_as_mapping = { 0, /* mp_ass_subscript */ }; +static PyObject * +mappingproxy_or(PyObject *self, PyObject *other) +{ + if (PyObject_TypeCheck(self, &PyDictProxy_Type)) { + return PyNumber_Or(((mappingproxyobject*)self)->mapping, other); + } + return PyNumber_Or(self, ((mappingproxyobject*)other)->mapping); +} + +static PyObject * +mappingproxy_ior(mappingproxyobject *self, PyObject *other) +{ + _Py_IDENTIFIER(copy); + PyObject *new = _PyObject_CallMethodIdNoArgs(self->mapping, &PyId_copy); + if (!new) { + return NULL; + } + PyObject *result = PyNumber_InPlaceOr(new, other); + Py_DECREF(new); + return result; +} + +static PyNumberMethods mappingproxy_as_number = { + .nb_or = mappingproxy_or, + .nb_inplace_or = (binaryfunc)mappingproxy_ior, +}; + static int mappingproxy_contains(mappingproxyobject *pp, PyObject *key) { @@ -1717,7 +1744,7 @@ PyTypeObject PyDictProxy_Type = { 0, /* tp_setattr */ 0, /* tp_as_async */ (reprfunc)mappingproxy_repr, /* tp_repr */ - 0, /* tp_as_number */ + &mappingproxy_as_number, /* tp_as_number */ &mappingproxy_as_sequence, /* tp_as_sequence */ &mappingproxy_as_mapping, /* tp_as_mapping */ 0, /* tp_hash */ From d79e99a10a2497c7990e50fe3456eae9bfe3aaab Mon Sep 17 00:00:00 2001 From: Brandt Bucher Date: Tue, 25 Feb 2020 09:37:30 -0800 Subject: [PATCH 3/9] Document MappingProxy operators. --- Doc/library/types.rst | 6 ++++++ .../next/Library/2020-02-25-09-28-06.bpo-36144.Rbvvi7.rst | 2 ++ 2 files changed, 8 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2020-02-25-09-28-06.bpo-36144.Rbvvi7.rst diff --git a/Doc/library/types.rst b/Doc/library/types.rst index 3529c2b0edb896..14e288c2103358 100644 --- a/Doc/library/types.rst +++ b/Doc/library/types.rst @@ -282,6 +282,12 @@ Standard names are defined for the following types: .. versionadded:: 3.3 + .. versionchanged:: 3.9 + + Updated to support the new union operators in :pep:`584`. The binary ``|`` + operator simply delegates to the mapping itself, while the in-place ``|=`` + operator delegates to a shallow copy of it (to enforce read-only behavior). + .. describe:: key in proxy Return ``True`` if the underlying mapping has a key *key*, else diff --git a/Misc/NEWS.d/next/Library/2020-02-25-09-28-06.bpo-36144.Rbvvi7.rst b/Misc/NEWS.d/next/Library/2020-02-25-09-28-06.bpo-36144.Rbvvi7.rst new file mode 100644 index 00000000000000..20e34127ad87b0 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2020-02-25-09-28-06.bpo-36144.Rbvvi7.rst @@ -0,0 +1,2 @@ +:class:`types.MappingProxyType` objects now support the merge (``|``) and update +(``|=``) operators from :pep:`584`. From 859477c72c46537d76c809a1247aaeaf44851d09 Mon Sep 17 00:00:00 2001 From: Brandt Bucher Date: Fri, 6 Mar 2020 11:03:20 -0800 Subject: [PATCH 4/9] Clean up mappingproxy_or. --- Objects/descrobject.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Objects/descrobject.c b/Objects/descrobject.c index 86f95d1d42cf06..c983e7768a2508 100644 --- a/Objects/descrobject.c +++ b/Objects/descrobject.c @@ -983,12 +983,15 @@ static PyMappingMethods mappingproxy_as_mapping = { }; static PyObject * -mappingproxy_or(PyObject *self, PyObject *other) +mappingproxy_or(PyObject *left, PyObject *right) { - if (PyObject_TypeCheck(self, &PyDictProxy_Type)) { - return PyNumber_Or(((mappingproxyobject*)self)->mapping, other); + if (PyObject_TypeCheck(left, &PyDictProxy_Type)) { + left = ((mappingproxyobject*)left)->mapping; } - return PyNumber_Or(self, ((mappingproxyobject*)other)->mapping); + if (PyObject_TypeCheck(right, &PyDictProxy_Type)) { + right = ((mappingproxyobject*)right)->mapping; + } + return PyNumber_Or(left, right); } static PyObject * @@ -1002,7 +1005,6 @@ mappingproxy_ior(mappingproxyobject *self, PyObject *other) PyObject *result = PyNumber_InPlaceOr(new, other); Py_DECREF(new); return result; -} static PyNumberMethods mappingproxy_as_number = { .nb_or = mappingproxy_or, From 5c7005b86524406fb3a66ac113455529cd5b3cef Mon Sep 17 00:00:00 2001 From: Brandt Bucher Date: Fri, 6 Mar 2020 11:07:19 -0800 Subject: [PATCH 5/9] Fix indent. --- Doc/library/types.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Doc/library/types.rst b/Doc/library/types.rst index 14e288c2103358..2c8772f2ce4e78 100644 --- a/Doc/library/types.rst +++ b/Doc/library/types.rst @@ -284,9 +284,9 @@ Standard names are defined for the following types: .. versionchanged:: 3.9 - Updated to support the new union operators in :pep:`584`. The binary ``|`` - operator simply delegates to the mapping itself, while the in-place ``|=`` - operator delegates to a shallow copy of it (to enforce read-only behavior). + Updated to support the new union operators in :pep:`584`. The binary ``|`` + operator simply delegates to the mapping itself, while the in-place ``|=`` + operator delegates to a shallow copy of it (to enforce read-only behavior). .. describe:: key in proxy From 7c952adba22455c25259beb3ef321b1d80a3eea6 Mon Sep 17 00:00:00 2001 From: Brandt Bucher Date: Fri, 6 Mar 2020 11:17:54 -0800 Subject: [PATCH 6/9] Whoops. --- Objects/descrobject.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Objects/descrobject.c b/Objects/descrobject.c index c983e7768a2508..b2720437768a24 100644 --- a/Objects/descrobject.c +++ b/Objects/descrobject.c @@ -1005,6 +1005,7 @@ mappingproxy_ior(mappingproxyobject *self, PyObject *other) PyObject *result = PyNumber_InPlaceOr(new, other); Py_DECREF(new); return result; +} static PyNumberMethods mappingproxy_as_number = { .nb_or = mappingproxy_or, From d5aa05ff483c2742cc9eb870aed4bbfe13aebd6b Mon Sep 17 00:00:00 2001 From: Brandt Bucher Date: Fri, 6 Mar 2020 21:51:13 -0800 Subject: [PATCH 7/9] Ditch |=. --- Lib/test/test_types.py | 8 ++------ Objects/descrobject.c | 10 ++-------- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index 6828945c51bdbd..544c91bc36a2a8 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -785,15 +785,11 @@ def test_union(self): with self.assertRaises(TypeError): [('r', 2), ('d', 2)] | view with self.assertRaises(TypeError): - view |= None + view |= [('r', 2), ('d', 2)] other = {'c': 3, 'p': 0} self.assertDictEqual(view | other, {'a': 0, 'b': 1, 'c': 3, 'p': 0}) self.assertDictEqual(other | view, {'c': 2, 'p': 0, 'a': 0, 'b': 1}) - view |= other - self.assertDictEqual(view, {'a': 0, 'b': 1, 'c': 3, 'p': 0}) - view = self.mappingproxy(mapping) - view |= iter((('c', 3), ('p', 0))) - self.assertDictEqual(view, {'a': 0, 'b': 1, 'c': 3, 'p': 0}) + self.assertEqual(view, {'a': 0, 'b': 1, 'c': 2}) self.assertDictEqual(mapping, {'a': 0, 'b': 1, 'c': 2}) self.assertDictEqual(other, {'c': 3, 'p': 0}) diff --git a/Objects/descrobject.c b/Objects/descrobject.c index b2720437768a24..312036db49e927 100644 --- a/Objects/descrobject.c +++ b/Objects/descrobject.c @@ -997,14 +997,8 @@ mappingproxy_or(PyObject *left, PyObject *right) static PyObject * mappingproxy_ior(mappingproxyobject *self, PyObject *other) { - _Py_IDENTIFIER(copy); - PyObject *new = _PyObject_CallMethodIdNoArgs(self->mapping, &PyId_copy); - if (!new) { - return NULL; - } - PyObject *result = PyNumber_InPlaceOr(new, other); - Py_DECREF(new); - return result; + PyErr_Format(PyExc_TypeError, + "'|=' is not supported by %s; use '|' instead", Py_TYPE(self)->tp_name); } static PyNumberMethods mappingproxy_as_number = { From 27f454912cbf79ae61f9c324ad53a61905ddc687 Mon Sep 17 00:00:00 2001 From: Brandt Bucher Date: Fri, 6 Mar 2020 21:55:06 -0800 Subject: [PATCH 8/9] Update docs. --- Doc/library/types.rst | 5 ++--- .../next/Library/2020-02-25-09-28-06.bpo-36144.Rbvvi7.rst | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Doc/library/types.rst b/Doc/library/types.rst index 2c8772f2ce4e78..4cb91c1a90bcfc 100644 --- a/Doc/library/types.rst +++ b/Doc/library/types.rst @@ -284,9 +284,8 @@ Standard names are defined for the following types: .. versionchanged:: 3.9 - Updated to support the new union operators in :pep:`584`. The binary ``|`` - operator simply delegates to the mapping itself, while the in-place ``|=`` - operator delegates to a shallow copy of it (to enforce read-only behavior). + Updated to support the new union (``|``) operator from :pep:`584`, which + simply delegates to the underlying mapping. .. describe:: key in proxy diff --git a/Misc/NEWS.d/next/Library/2020-02-25-09-28-06.bpo-36144.Rbvvi7.rst b/Misc/NEWS.d/next/Library/2020-02-25-09-28-06.bpo-36144.Rbvvi7.rst index 20e34127ad87b0..da0ff9d9ff894a 100644 --- a/Misc/NEWS.d/next/Library/2020-02-25-09-28-06.bpo-36144.Rbvvi7.rst +++ b/Misc/NEWS.d/next/Library/2020-02-25-09-28-06.bpo-36144.Rbvvi7.rst @@ -1,2 +1,2 @@ -:class:`types.MappingProxyType` objects now support the merge (``|``) and update -(``|=``) operators from :pep:`584`. +:class:`types.MappingProxyType` objects now support the merge (``|``) operator +from :pep:`584`. From 4ab1fc5a54f9866310d47a0dd3cb2d79424cef6f Mon Sep 17 00:00:00 2001 From: Brandt Bucher Date: Fri, 6 Mar 2020 21:59:07 -0800 Subject: [PATCH 9/9] Silence warnings. --- Objects/descrobject.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Objects/descrobject.c b/Objects/descrobject.c index 312036db49e927..98c924e9d5623b 100644 --- a/Objects/descrobject.c +++ b/Objects/descrobject.c @@ -995,15 +995,15 @@ mappingproxy_or(PyObject *left, PyObject *right) } static PyObject * -mappingproxy_ior(mappingproxyobject *self, PyObject *other) +mappingproxy_ior(PyObject *self, PyObject *Py_UNUSED(other)) { - PyErr_Format(PyExc_TypeError, + return PyErr_Format(PyExc_TypeError, "'|=' is not supported by %s; use '|' instead", Py_TYPE(self)->tp_name); } static PyNumberMethods mappingproxy_as_number = { .nb_or = mappingproxy_or, - .nb_inplace_or = (binaryfunc)mappingproxy_ior, + .nb_inplace_or = mappingproxy_ior, }; static int