8000 gh-108191: Add support of positional argument in SimpleNamespace cons… · python/cpython@c761180 · GitHub
[go: up one dir, main page]

Skip to content

Commit c761180

Browse files
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).
1 parent 8f3d09b commit c761180

File tree

5 files changed

+82
-8
lines changed

5 files changed

+82
-8
lines changed

Doc/library/types.rst

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -480,13 +480,20 @@ Additional Utility Classes and Functions
480480
namespace, as well as a meaningful repr.
481481

482482
Unlike :class:`object`, with ``SimpleNamespace`` you can add and remove
483-
attributes. If a ``SimpleNamespace`` object is initialized with keyword
483+
attributes.
484+
The :class:`!SimpleNamespace` constructor can take a positional argument
485+
which must be a mapping object with string keys or an :term:`iterable`
486+
object producing key-value pairs with string keys.
487+
Key-value pairs from the mapping object or the iterable object are
488+
directly added to the underlying namespace.
489+
If a ``SimpleNamespace`` object is initialized with keyword
484490
arguments, those are directly added to the underlying namespace.
485491

486492
The type is roughly equivalent to the following code::
487493

488494
class SimpleNamespace:
489-
def __init__(self, /, **kwargs):
495+
def __init__(self, mapping_or_iterable=(), /, **kwargs):
496+
self.__dict__.update(mapping_or_iterable)
490497
self.__dict__.update(kwargs)
491498

492499
def __repr__(self):
@@ -508,6 +515,9 @@ Additional Utility Classes and Functions
508515
Attribute order in the repr changed from alphabetical to insertion (like
509516
``dict``).
510517

518+
.. versionchanged:: 3.13
519+
Added support of an optional positional argument.
520+
511521
.. function:: DynamicClassAttribute(fget=None, fset=None, fdel=None, doc=None)
512522

513523
Route attribute access on a class to __getattr__.

Doc/whatsnew/3.13.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,14 @@ traceback
160160
to format the nested exceptions of a :exc:`BaseExceptionGroup` instance, recursively.
161161
(Contributed by Irit Katriel in :gh:`105292`.)
162162

163+
types
164+
-----
165+
166+
* :class:`~types.SimpleNamespace` constructor allows now to specify initial
167+
values of attributes as a positional argument which must be a mapping or
168+
an iterable or key-value pairs.
169+
(Contributed by Serhiy Storchaka in :gh:`108191`.)
170+
163171
typing
164172
------
165173

Lib/test/test_types.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from test.support import run_with_locale, cpython_only
44
import collections.abc
5-
from collections import namedtuple
5+
from collections import namedtuple, UserDict
66
import copy
77
import gc
88
import inspect
@@ -1732,18 +1732,35 @@ def test_constructor(self):
17321732
ns1 = types.SimpleNamespace()
17331733
ns2 = types.SimpleNamespace(x=1, y=2)
17341734
ns3 = types.SimpleNamespace(**dict(x=1, y=2))
1735+
ns4 = types.SimpleNamespace({'x': 1, 'y': 2}, x=4, z=3)
1736+
ns5 = types.SimpleNamespace([['x', 1], ['y', 2]], x=4, z=3)
1737+
ns6 = types.SimpleNamespace(UserDict({'x': 1, 'y': 2}), x=4, z=3)
17351738

1739+
with self.assertRaises(TypeError):
1740+
types.SimpleNamespace([], [])
17361741
with self.assertRaises(TypeError):
17371742
types.SimpleNamespace(1, 2, 3)
17381743
with self.assertRaises(TypeError):
17391744
types.SimpleNamespace(**{1: 2})
1745+
with self.assertRaises(TypeError):
1746+
types.SimpleNamespace({1: 2})
1747+
with self.assertRaises(TypeError):
1748+
types.SimpleNamespace([[1, 2]])
1749+
with self.assertRaises(TypeError):
1750+
types.SimpleNamespace(UserDict({1: 2}))
17401751

17411752
self.assertEqual(len(ns1.__dict__), 0)
17421753
self.assertEqual(vars(ns1), {})
17431754
self.assertEqual(len(ns2.__dict__), 2)
17441755
self.assertEqual(vars(ns2), {'y': 2, 'x': 1})
17451756
self.assertEqual(len(ns3.__dict__), 2)
17461757
self.assertEqual(vars(ns3), {'y': 2, 'x': 1})
1758+
self.assertEqual(len(ns4.__dict__), 3)
1759+
self.assertEqual(vars(ns4), {'x': 4, 'y': 2, 'z': 3})
1760+
self.assertEqual(len(ns5.__dict__), 3)
1761+
self.assertEqual(vars(ns5), {'x': 4, 'y': 2, 'z': 3})
1762+
self.assertEqual(len(ns6.__dict__), 3)
1763+
self.assertEqual(vars(ns6), {'x': 4, 'y': 2, 'z': 3})
17471764

17481765
def test_unbound(self):
17491766
ns1 = vars(types.SimpleNamespace())
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
The :class:`types.SimpleNamespace` now accepts an optional positional
2+
argument which specifies initial values of attributes as a dict or an
3+
iterable of key-value pairs.

Objects/namespaceobject.c

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
#include "Python.h"
44
#include "pycore_namespace.h" // _PyNamespace_Type
5+
#include "pycore_global_strings.h" // _Py_ID
6+
#include "pycore_global_objects.h" // _Py_ID
7+
#include "pycore_runtime.h" // _Py_ID
58

69
#include <stddef.h> // offsetof()
710

@@ -43,10 +46,42 @@ namespace_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
4346
static int
4447
namespace_init(_PyNamespaceObject *ns, PyObject *args, PyObject *kwds)
4548
{
46-
if (PyTuple_GET_SIZE(args) != 0) {
47-
PyErr_Format(PyExc_TypeError, "no positional arguments expected");
49+
PyObject *arg = NULL;
50+
if (!PyArg_UnpackTuple(args, _PyType_Name(Py_TYPE(ns)), 0, 1, &arg)) {
4851
return -1;
4952
}
53+
if (arg != NULL) {
54+
PyObject *dict;
55+
int ret = 0;
56+
if (PyDict_CheckExact(arg)) {
57+
dict = Py_NewRef(arg);
58+
}
59+
else {
60+
dict = PyDict_New();
61+
if (dict == NULL) {
62+
return -1;
63+
}
64+
PyObject *func;
65+
ret = PyObject_GetOptionalAttr(arg, &_Py_ID(keys), &func);
66+
if (ret > 0) {
67+
Py_DECREF(func);
68+
ret = PyDict_Merge(dict, arg, 1);
69+
}
70+
else if (ret == 0) {
71+
ret = PyDict_MergeFromSeq2(dict, arg, 1);
72+
}
73+
}
74+
if (ret < 0 ||
75+
!PyArg_ValidateKeywordArguments(dict) ||
76+
PyDict_Update(ns->ns_dict, dict) < 0)
77+
{
78+
ret = -1;
79+
}
80+
Py_DECREF(dict);
81+
if (ret < 0) {
82+
return -1;
83+
}
84+
}
5085
if (kwds == NULL) {
5186
return 0;
5287
}
@@ -197,9 +232,10 @@ static PyMethodDef namespace_methods[] = {
197232

198233

199234
PyDoc_STRVAR(namespace_doc,
200-
"A simple attribute-based namespace.\n\
201-
\n\
202-
SimpleNamespace(**kwargs)");
235+
"SimpleNamespace(mapping_or_iterable=(), /, **kwargs)\n"
236+
"--\n"
237+
"\n"
238+
"A simple attribute-based namespace.");
203239

204240
PyTypeObject _PyNamespace_Type = {
205241
PyVarObject_HEAD_INIT(&PyType_Type, 0)

0 commit comments

Comments
 (0)
0