From c761180d17405d0ddb8ae8058ac6b4a98dc9686f Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 21 Aug 2023 09:43:56 +0300 Subject: [PATCH 01/13] gh-108191: Add support of positional argument in SimpleNamespace constructor SimpleNamespace({'a': 1, 'b': 2}) and SimpleNamespace([('a', 1), ('b', 2)]) are now the same as SimpleNamespace(a=1, b=2). --- Doc/library/types.rst | 14 +++++- Doc/whatsnew/3.13.rst | 8 ++++ Lib/test/test_types.py | 19 +++++++- ...-08-21-10-34-43.gh-issue-108191.GZM3mv.rst | 3 ++ Objects/namespaceobject.c | 46 +++++++++++++++++-- 5 files changed, 82 insertions(+), 8 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-08-21-10-34-43.gh-issue-108191.GZM3mv.rst diff --git a/Doc/library/types.rst b/Doc/library/types.rst index 8cbe17df16f107..1de80a2daeedbf 100644 --- a/Doc/library/types.rst +++ b/Doc/library/types.rst @@ -480,13 +480,20 @@ Additional Utility Classes and Functions namespace, as well as a meaningful repr. Unlike :class:`object`, with ``SimpleNamespace`` you can add and remove - attributes. If a ``SimpleNamespace`` object is initialized with keyword + attributes. + The :class:`!SimpleNamespace` constructor can take a positional argument + which must be a mapping object with string keys or an :term:`iterable` + object producing key-value pairs with string keys. + Key-value pairs from the mapping object or the iterable object are + directly added to the underlying namespace. + If a ``SimpleNamespace`` object is initialized with keyword arguments, those are directly added to the underlying namespace. The type is roughly equivalent to the following code:: class SimpleNamespace: - def __init__(self, /, **kwargs): + def __init__(self, mapping_or_iterable=(), /, **kwargs): + self.__dict__.update(mapping_or_iterable) self.__dict__.update(kwargs) def __repr__(self): @@ -508,6 +515,9 @@ Additional Utility Classes and Functions Attribute order in the repr changed from alphabetical to insertion (like ``dict``). + .. versionchanged:: 3.13 + Added support of an optional positional argument. + .. function:: DynamicClassAttribute(fget=None, fset=None, fdel=None, doc=None) Route attribute access on a class to __getattr__. diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 47b868bad31923..d346fd42dffed5 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -160,6 +160,14 @@ traceback to format the nested exceptions of a :exc:`BaseExceptionGroup` instance, recursively. (Contributed by Irit Katriel in :gh:`105292`.) +types +----- + +* :class:`~types.SimpleNamespace` constructor allows now to specify initial + values of attributes as a positional argument which must be a mapping or + an iterable or key-value pairs. + (Contributed by Serhiy Storchaka in :gh:`108191`.) + typing ------ diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index f2efee90dc0240..f100081697ddf2 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -2,7 +2,7 @@ from test.support import run_with_locale, cpython_only import collections.abc -from collections import namedtuple +from collections import namedtuple, UserDict import copy import gc import inspect @@ -1732,11 +1732,22 @@ def test_constructor(self): ns1 = types.SimpleNamespace() ns2 = types.SimpleNamespace(x=1, y=2) ns3 = types.SimpleNamespace(**dict(x=1, y=2)) + ns4 = types.SimpleNamespace({'x': 1, 'y': 2}, x=4, z=3) + ns5 = types.SimpleNamespace([['x', 1], ['y', 2]], x=4, z=3) + ns6 = types.SimpleNamespace(UserDict({'x': 1, 'y': 2}), x=4, z=3) + with self.assertRaises(TypeError): + types.SimpleNamespace([], []) with self.assertRaises(TypeError): types.SimpleNamespace(1, 2, 3) with self.assertRaises(TypeError): types.SimpleNamespace(**{1: 2}) + with self.assertRaises(TypeError): + types.SimpleNamespace({1: 2}) + with self.assertRaises(TypeError): + types.SimpleNamespace([[1, 2]]) + with self.assertRaises(TypeError): + types.SimpleNamespace(UserDict({1: 2})) self.assertEqual(len(ns1.__dict__), 0) self.assertEqual(vars(ns1), {}) @@ -1744,6 +1755,12 @@ def test_constructor(self): self.assertEqual(vars(ns2), {'y': 2, 'x': 1}) self.assertEqual(len(ns3.__dict__), 2) self.assertEqual(vars(ns3), {'y': 2, 'x': 1}) + self.assertEqual(len(ns4.__dict__), 3) + self.assertEqual(vars(ns4), {'x': 4, 'y': 2, 'z': 3}) + self.assertEqual(len(ns5.__dict__), 3) + self.assertEqual(vars(ns5), {'x': 4, 'y': 2, 'z': 3}) + self.assertEqual(len(ns6.__dict__), 3) + self.assertEqual(vars(ns6), {'x': 4, 'y': 2, 'z': 3}) def test_unbound(self): ns1 = vars(types.SimpleNamespace()) diff --git a/Misc/NEWS.d/next/Library/2023-08-21-10-34-43.gh-issue-108191.GZM3mv.rst b/Misc/NEWS.d/next/Library/2023-08-21-10-34-43.gh-issue-108191.GZM3mv.rst new file mode 100644 index 00000000000000..da4ce5742549e6 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-08-21-10-34-43.gh-issue-108191.GZM3mv.rst @@ -0,0 +1,3 @@ +The :class:`types.SimpleNamespace` now accepts an optional positional +argument which specifies initial values of attributes as a dict or an +iterable of key-value pairs. diff --git a/Objects/namespaceobject.c b/Objects/namespaceobject.c index 11cf859add3ab8..9c38170bba326a 100644 --- a/Objects/namespaceobject.c +++ b/Objects/namespaceobject.c @@ -2,6 +2,9 @@ #include "Python.h" #include "pycore_namespace.h" // _PyNamespace_Type +#include "pycore_global_strings.h" // _Py_ID +#include "pycore_global_objects.h" // _Py_ID +#include "pycore_runtime.h" // _Py_ID #include // offsetof() @@ -43,10 +46,42 @@ namespace_new(PyTypeObject *type, PyObject *args, PyObject *kwds) static int namespace_init(_PyNamespaceObject *ns, PyObject *args, PyObject *kwds) { - if (PyTuple_GET_SIZE(args) != 0) { - PyErr_Format(PyExc_TypeError, "no positional arguments expected"); + PyObject *arg = NULL; + if (!PyArg_UnpackTuple(args, _PyType_Name(Py_TYPE(ns)), 0, 1, &arg)) { return -1; } + if (arg != NULL) { + PyObject *dict; + int ret = 0; + if (PyDict_CheckExact(arg)) { + dict = Py_NewRef(arg); + } + else { + dict = PyDict_New(); + if (dict == NULL) { + return -1; + } + PyObject *func; + ret = PyObject_GetOptionalAttr(arg, &_Py_ID(keys), &func); + if (ret > 0) { + Py_DECREF(func); + ret = PyDict_Merge(dict, arg, 1); + } + else if (ret == 0) { + ret = PyDict_MergeFromSeq2(dict, arg, 1); + } + } + if (ret < 0 || + !PyArg_ValidateKeywordArguments(dict) || + PyDict_Update(ns->ns_dict, dict) < 0) + { + ret = -1; + } + Py_DECREF(dict); + if (ret < 0) { + return -1; + } + } if (kwds == NULL) { return 0; } @@ -197,9 +232,10 @@ static PyMethodDef namespace_methods[] = { PyDoc_STRVAR(namespace_doc, -"A simple attribute-based namespace.\n\ -\n\ -SimpleNamespace(**kwargs)"); +"SimpleNamespace(mapping_or_iterable=(), /, **kwargs)\n" +"--\n" +"\n" +"A simple attribute-based namespace."); PyTypeObject _PyNamespace_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) From 06197a2459b105b90d8035ae34db8b6ab0ea110d Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 22 Aug 2023 10:26:48 +0300 Subject: [PATCH 02/13] Update Objects/namespaceobject.c Co-authored-by: Erlend E. Aasland --- Objects/namespaceobject.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/Objects/namespaceobject.c b/Objects/namespaceobject.c index 9c38170bba326a..24376bd225f480 100644 --- a/Objects/namespaceobject.c +++ b/Objects/namespaceobject.c @@ -2,8 +2,6 @@ #include "Python.h" #include "pycore_namespace.h" // _PyNamespace_Type -#include "pycore_global_strings.h" // _Py_ID -#include "pycore_global_objects.h" // _Py_ID #include "pycore_runtime.h" // _Py_ID #include // offsetof() From 1127a873ef50e17f9df3a47b36cf3c3f0601b6a6 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Fri, 25 Aug 2023 00:14:11 +0300 Subject: [PATCH 03/13] Simplify the code. --- Objects/namespaceobject.c | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/Objects/namespaceobject.c b/Objects/namespaceobject.c index 24376bd225f480..8f547fadd0103e 100644 --- a/Objects/namespaceobject.c +++ b/Objects/namespaceobject.c @@ -2,7 +2,6 @@ #include "Python.h" #include "pycore_namespace.h" // _PyNamespace_Type -#include "pycore_runtime.h" // _Py_ID #include // offsetof() @@ -55,19 +54,10 @@ namespace_init(_PyNamespaceObject *ns, PyObject *args, PyObject *kwds) dict = Py_NewRef(arg); } else { - dict = PyDict_New(); + dict = PyObject_CallOneArg((PyObject *)&PyDict_Type, arg); if (dict == NULL) { return -1; } - PyObject *func; - ret = PyObject_GetOptionalAttr(arg, &_Py_ID(keys), &func); - if (ret > 0) { - Py_DECREF(func); - ret = PyDict_Merge(dict, arg, 1); - } - else if (ret == 0) { - ret = PyDict_MergeFromSeq2(dict, arg, 1); - } } if (ret < 0 || !PyArg_ValidateKeywordArguments(dict) || From bc7ee1ce28c2ae1da5b3e9ab038dd5b76ae74034 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Fri, 25 Aug 2023 00:15:53 +0300 Subject: [PATCH 04/13] Update Objects/namespaceobject.c --- Objects/namespaceobject.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Objects/namespaceobject.c b/Objects/namespaceobject.c index 8f547fadd0103e..cad535853d172d 100644 --- a/Objects/namespaceobject.c +++ b/Objects/namespaceobject.c @@ -59,8 +59,7 @@ namespace_init(_PyNamespaceObject *ns, PyObject *args, PyObject *kwds) return -1; } } - if (ret < 0 || - !PyArg_ValidateKeywordArguments(dict) || + if (!PyArg_ValidateKeywordArguments(dict) || PyDict_Update(ns->ns_dict, dict) < 0) { ret = -1; From e94e97afd1b0c3cac2fe3138a86fbf7b3802c7cb Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sat, 26 Aug 2023 11:06:39 +0300 Subject: [PATCH 05/13] Polishing. --- Objects/namespaceobject.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Objects/namespaceobject.c b/Objects/namespaceobject.c index cad535853d172d..8060089f9082c6 100644 --- a/Objects/namespaceobject.c +++ b/Objects/namespaceobject.c @@ -49,7 +49,6 @@ namespace_init(_PyNamespaceObject *ns, PyObject *args, PyObject *kwds) } if (arg != NULL) { PyObject *dict; - int ret = 0; if (PyDict_CheckExact(arg)) { dict = Py_NewRef(arg); } @@ -62,12 +61,10 @@ namespace_init(_PyNamespaceObject *ns, PyObject *args, PyObject *kwds) if (!PyArg_ValidateKeywordArguments(dict) || PyDict_Update(ns->ns_dict, dict) < 0) { - ret = -1; - } - Py_DECREF(dict); - if (ret < 0) { + Py_DECREF(dict); return -1; } + Py_DECREF(dict); } if (kwds == NULL) { return 0; From f1765679a437327bfcfee3e210510f31d0e555a9 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sat, 26 Aug 2023 11:09:40 +0300 Subject: [PATCH 06/13] Extend tests. --- Lib/test/test_types.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index f100081697ddf2..e068999c4b1944 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -1741,13 +1741,15 @@ def test_constructor(self): with self.assertRaises(TypeError): types.SimpleNamespace(1, 2, 3) with self.assertRaises(TypeError): - types.SimpleNamespace(**{1: 2}) + types.SimpleNamespace(**{1: 2}) # non-string key with self.assertRaises(TypeError): types.SimpleNamespace({1: 2}) with self.assertRaises(TypeError): types.SimpleNamespace([[1, 2]]) with self.assertRaises(TypeError): types.SimpleNamespace(UserDict({1: 2})) + with self.assertRaises(TypeError): + types.SimpleNamespace([[[], 2]]) # non-hashable key self.assertEqual(len(ns1.__dict__), 0) self.assertEqual(vars(ns1), {}) From cf7ac97fcae88335f50d4169395aee88c16dcdb2 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 29 Aug 2023 21:27:33 +0300 Subject: [PATCH 07/13] Apply suggestions from code review Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com> --- Doc/library/types.rst | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/Doc/library/types.rst b/Doc/library/types.rst index 1de80a2daeedbf..c51f9d82a7ef87 100644 --- a/Doc/library/types.rst +++ b/Doc/library/types.rst @@ -479,15 +479,18 @@ Additional Utility Classes and Functions A simple :class:`object` subclass that provides attribute access to its namespace, as well as a meaningful repr. - Unlike :class:`object`, with ``SimpleNamespace`` you can add and remove + Unlike :class:`object`, with :class:`!SimpleNamespace` you can add and remove attributes. - The :class:`!SimpleNamespace` constructor can take a positional argument - which must be a mapping object with string keys or an :term:`iterable` - object producing key-value pairs with string keys. - Key-value pairs from the mapping object or the iterable object are - directly added to the underlying namespace. - If a ``SimpleNamespace`` object is initialized with keyword - arguments, those are directly added to the underlying namespace. + + :py:class:`SimpleNamespace` objects may be initialized either + with keyword arguments or with a single positional argument. + When initialized with keyword arguments, + those are directly added to the underlying namespace. + Alternatively, when initialized with a positional argument, + the underlying namespace will be updated with key-value pairs + from that argument (either a mapping object or + an :term:`iterable` object producing key-value pairs). + All such keys must be strings. The type is roughly equivalent to the following code:: From 04760112d5ebd475cc44bca3308ab8f330afb8f6 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 13 Sep 2023 14:14:57 +0300 Subject: [PATCH 08/13] Apply suggestions from code review Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com> --- Doc/library/types.rst | 2 +- Doc/whatsnew/3.13.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/library/types.rst b/Doc/library/types.rst index c51f9d82a7ef87..a5fda6ef4f6d28 100644 --- a/Doc/library/types.rst +++ b/Doc/library/types.rst @@ -519,7 +519,7 @@ Additional Utility Classes and Functions ``dict``). .. versionchanged:: 3.13 - Added support of an optional positional argument. + Added support for an optional positional argument. .. function:: DynamicClassAttribute(fget=None, fset=None, fdel=None, doc=None) diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index b5b40620bd348c..0c2a885662c16a 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -184,7 +184,7 @@ traceback types ----- -* :class:`~types.SimpleNamespace` constructor allows now to specify initial +* :class:`~types.SimpleNamespace` constructor now allows specifying initial values of attributes as a positional argument which must be a mapping or an iterable or key-value pairs. (Contributed by Serhiy Storchaka in :gh:`108191`.) From 299d437f02fc1332f9c9a4f773875a93280c69cc Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 7 Mar 2024 12:48:22 +0200 Subject: [PATCH 09/13] Apply suggestions from code review Co-authored-by: Eric Snow Co-authored-by: Pieter Eendebak --- Doc/library/types.rst | 5 +++-- Doc/whatsnew/3.13.rst | 2 +- Lib/test/test_types.py | 6 ++++++ 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/Doc/library/types.rst b/Doc/library/types.rst index 24daf245ab3bcc..2fb1f5c8a79ccb 100644 --- a/Doc/library/types.rst +++ b/Doc/library/types.rst @@ -488,8 +488,9 @@ Additional Utility Classes and Functions Unlike :class:`object`, with :class:`!SimpleNamespace` you can add and remove attributes. - :py:class:`SimpleNamespace` objects may be initialized either - with keyword arguments or with a single positional argument. + :py:class:`SimpleNamespace` objects may be initialized + in the same way as :class:`dict`: either with keyword arguments, + with a single positional argument, or with both. When initialized with keyword arguments, those are directly added to the underlying namespace. Alternatively, when initialized with a positional argument, diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 6d4eb5b9af9dbe..592b2be1d68d2c 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -410,7 +410,7 @@ types * :class:`~types.SimpleNamespace` constructor now allows specifying initial values of attributes as a positional argument which must be a mapping or - an iterable or key-value pairs. + an iterable of key-value pairs. (Contributed by Serhiy Storchaka in :gh:`108191`.) typing diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index 83a69fdfabfded..da55d671da7114 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -1741,6 +1741,12 @@ def test_constructor(self): ns4 = types.SimpleNamespace({'x': 1, 'y': 2}, x=4, z=3) ns5 = types.SimpleNamespace([['x', 1], ['y', 2]], x=4, z=3) ns6 = types.SimpleNamespace(UserDict({'x': 1, 'y': 2}), x=4, z=3) + ns7 = types.SimpleNamespace({'x': 1, 'y': 2}) + ns8 = types.SimpleNamespace([['x', 1], ['y', 2]]) + ns9 = types.SimpleNamespace([], x=4, z=3) + ns10 = types.SimpleNamespace({}, x=4, z=3) + ns11 = types.SimpleNamespace([]) + ns12 = types.SimpleNamespace({}) with self.assertRaises(TypeError): types.SimpleNamespace([], []) From 3c2002dc5ec4b3441e35e11b8ef249f65f40f87c Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 7 Mar 2024 13:05:22 +0200 Subject: [PATCH 10/13] Update tests. --- Lib/test/test_types.py | 48 ++++++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index 6503148e0aad39..d267633d37c52f 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -1755,18 +1755,29 @@ class Model(metaclass=ModelBase): class SimpleNamespaceTests(unittest.TestCase): def test_constructor(self): - ns1 = types.SimpleNamespace() - ns2 = types.SimpleNamespace(x=1, y=2) - ns3 = types.SimpleNamespace(**dict(x=1, y=2)) - ns4 = types.SimpleNamespace({'x': 1, 'y': 2}, x=4, z=3) - ns5 = types.SimpleNamespace([['x', 1], ['y', 2]], x=4, z=3) - ns6 = types.SimpleNamespace(UserDict({'x': 1, 'y': 2}), x=4, z=3) - ns7 = types.SimpleNamespace({'x': 1, 'y': 2}) - ns8 = types.SimpleNamespace([['x', 1], ['y', 2]]) - ns9 = types.SimpleNamespace([], x=4, z=3) - ns10 = types.SimpleNamespace({}, x=4, z=3) - ns11 = types.SimpleNamespace([]) - ns12 = types.SimpleNamespace({}) + def check(ns, expected): + self.assertEqual(len(ns.__dict__), len(expected)) + self.assertEqual(vars(ns), expected) + # check order + self.assertEqual(list(vars(ns).items()), list(expected.items())) + for name in expected: + self.assertEqual(getattr(ns, name), expected[name]) + + check(types.SimpleNamespace(), {}) + check(types.SimpleNamespace(x=1, y=2), {'x': 1, 'y': 2}) + check(types.SimpleNamespace(**dict(x=1, y=2)), {'x': 1, 'y': 2}) + check(types.SimpleNamespace({'x': 1, 'y': 2}, x=4, z=3), + {'x': 4, 'y': 2, 'z': 3}) + check(types.SimpleNamespace([['x', 1], ['y', 2]], x=4, z=3), + {'x': 4, 'y': 2, 'z': 3}) + check(types.SimpleNamespace(UserDict({'x': 1, 'y': 2}), x=4, z=3), + {'x': 4, 'y': 2, 'z': 3}) + check(types.SimpleNamespace({'x': 1, 'y': 2}), {'x': 1, 'y': 2}) + check(types.SimpleNamespace([['x', 1], ['y', 2]]), {'x': 1, 'y': 2}) + check(types.SimpleNamespace([], x=4, z=3), {'x': 4, 'z': 3}) + check(types.SimpleNamespace({}, x=4, z=3), {'x': 4, 'z': 3}) + check(types.SimpleNamespace([]), {}) + check(types.SimpleNamespace({}), {}) with self.assertRaises(TypeError): types.SimpleNamespace([], []) @@ -1783,19 +1794,6 @@ def test_constructor(self): with self.assertRaises(TypeError): types.SimpleNamespace([[[], 2]]) # non-hashable key - self.assertEqual(len(ns1.__dict__), 0) - self.assertEqual(vars(ns1), {}) - self.assertEqual(len(ns2.__dict__), 2) - self.assertEqual(vars(ns2), {'y': 2, 'x': 1}) - self.assertEqual(len(ns3.__dict__), 2) - self.assertEqual(vars(ns3), {'y': 2, 'x': 1}) - self.assertEqual(len(ns4.__dict__), 3) - self.assertEqual(vars(ns4), {'x': 4, 'y': 2, 'z': 3}) - self.assertEqual(len(ns5.__dict__), 3) - self.assertEqual(vars(ns5), {'x': 4, 'y': 2, 'z': 3}) - self.assertEqual(len(ns6.__dict__), 3) - self.assertEqual(vars(ns6), {'x': 4, 'y': 2, 'z': 3}) - def test_unbound(self): ns1 = vars(types.SimpleNamespace()) ns2 = vars(types.SimpleNamespace(x=1, y=2)) From e7ad7be0a76caacabd2d26f3c203dd8c90d92737 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 7 Mar 2024 13:09:08 +0200 Subject: [PATCH 11/13] Update Objects/namespaceobject.c Co-authored-by: Eric Snow --- Objects/namespaceobject.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Objects/namespaceobject.c b/Objects/namespaceobject.c index 482d4b4c41a324..3552e21da39bc8 100644 --- a/Objects/namespaceobject.c +++ b/Objects/namespaceobject.c @@ -58,13 +58,12 @@ namespace_init(_PyNamespaceObject *ns, PyObject *args, PyObject *kwds) return -1; } } - if (!PyArg_ValidateKeywordArguments(dict) || - PyDict_Update(ns->ns_dict, dict) < 0) - { - Py_DECREF(dict); + int err = (!PyArg_ValidateKeywordArguments(dict) || + PyDict_Update(ns->ns_dict, dict) < 0); + Py_DECREF(dict); + if {err) { return -1; } - Py_DECREF(dict); } if (kwds == NULL) { return 0; From 772b708452a7e37de6bf52754847a28c39b6d406 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 7 Mar 2024 13:19:36 +0200 Subject: [PATCH 12/13] Extend errors tests. --- Lib/test/test_types.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index d267633d37c52f..fbca198aab5180 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -1780,9 +1780,15 @@ def check(ns, expected): check(types.SimpleNamespace({}), {}) with self.assertRaises(TypeError): - types.SimpleNamespace([], []) + types.SimpleNamespace([], []) # too many positional arguments with self.assertRaises(TypeError): - types.SimpleNamespace(1, 2, 3) + types.SimpleNamespace(1) # not a mapping or iterable + with self.assertRaises(TypeError): + types.SimpleNamespace([1]) # non-iterable + with self.assertRaises(ValueError): + types.SimpleNamespace([['x']]) # not a pair + with self.assertRaises(ValueError): + types.SimpleNamespace([['x', 'y', 'z']]) with self.assertRaises(TypeError): types.SimpleNamespace(**{1: 2}) # non-string key with self.assertRaises(TypeError): From 54b2acf9c1bf819e013fe4d44dacb947a0529681 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 7 Mar 2024 13:26:30 +0200 Subject: [PATCH 13/13] Fix typo. --- Objects/namespaceobject.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/namespaceobject.c b/Objects/namespaceobject.c index 3552e21da39bc8..8a28c94f5cbf28 100644 --- a/Objects/namespaceobject.c +++ b/Objects/namespaceobject.c @@ -61,7 +61,7 @@ namespace_init(_PyNamespaceObject *ns, PyObject *args, PyObject *kwds) int err = (!PyArg_ValidateKeywordArguments(dict) || PyDict_Update(ns->ns_dict, dict) < 0); Py_DECREF(dict); - if {err) { + if (err) { return -1; } }