mirror of https://github.com/python/cpython
gh-108191: Add support of positional argument in SimpleNamespace constructor (GH-108195)
SimpleNamespace({'a': 1, 'b': 2}) and SimpleNamespace([('a', 1), ('b', 2)]) are now the same as SimpleNamespace(a=1, b=2).
This commit is contained in:
parent
85ec1c2dc6
commit
93b7ed7c6b
|
@ -481,14 +481,25 @@ Additional Utility Classes and Functions
|
||||||
A simple :class:`object` subclass that provides attribute access to its
|
A simple :class:`object` subclass that provides attribute access to its
|
||||||
namespace, as well as a meaningful repr.
|
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. If a ``SimpleNamespace`` object is initialized with keyword
|
attributes.
|
||||||
arguments, those are directly added to the underlying namespace.
|
|
||||||
|
: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,
|
||||||
|
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::
|
The type is roughly equivalent to the following code::
|
||||||
|
|
||||||
class SimpleNamespace:
|
class SimpleNamespace:
|
||||||
def __init__(self, /, **kwargs):
|
def __init__(self, mapping_or_iterable=(), /, **kwargs):
|
||||||
|
self.__dict__.update(mapping_or_iterable)
|
||||||
self.__dict__.update(kwargs)
|
self.__dict__.update(kwargs)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
|
@ -512,6 +523,9 @@ Additional Utility Classes and Functions
|
||||||
Attribute order in the repr changed from alphabetical to insertion (like
|
Attribute order in the repr changed from alphabetical to insertion (like
|
||||||
``dict``).
|
``dict``).
|
||||||
|
|
||||||
|
.. versionchanged:: 3.13
|
||||||
|
Added support for an optional positional argument.
|
||||||
|
|
||||||
.. function:: DynamicClassAttribute(fget=None, fset=None, fdel=None, doc=None)
|
.. function:: DynamicClassAttribute(fget=None, fset=None, fdel=None, doc=None)
|
||||||
|
|
||||||
Route attribute access on a class to __getattr__.
|
Route attribute access on a class to __getattr__.
|
||||||
|
|
|
@ -804,6 +804,14 @@ traceback
|
||||||
``True``) to indicate whether ``exc_type`` should be saved.
|
``True``) to indicate whether ``exc_type`` should be saved.
|
||||||
(Contributed by Irit Katriel in :gh:`112332`.)
|
(Contributed by Irit Katriel in :gh:`112332`.)
|
||||||
|
|
||||||
|
types
|
||||||
|
-----
|
||||||
|
|
||||||
|
* :class:`~types.SimpleNamespace` constructor now allows specifying initial
|
||||||
|
values of attributes as a positional argument which must be a mapping or
|
||||||
|
an iterable of key-value pairs.
|
||||||
|
(Contributed by Serhiy Storchaka in :gh:`108191`.)
|
||||||
|
|
||||||
typing
|
typing
|
||||||
------
|
------
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
from test.support import run_with_locale, cpython_only, MISSING_C_DOCSTRINGS
|
from test.support import run_with_locale, cpython_only, MISSING_C_DOCSTRINGS
|
||||||
import collections.abc
|
import collections.abc
|
||||||
from collections import namedtuple
|
from collections import namedtuple, UserDict
|
||||||
import copy
|
import copy
|
||||||
import _datetime
|
import _datetime
|
||||||
import gc
|
import gc
|
||||||
|
@ -1755,21 +1755,50 @@ class ClassCreationTests(unittest.TestCase):
|
||||||
class SimpleNamespaceTests(unittest.TestCase):
|
class SimpleNamespaceTests(unittest.TestCase):
|
||||||
|
|
||||||
def test_constructor(self):
|
def test_constructor(self):
|
||||||
ns1 = types.SimpleNamespace()
|
def check(ns, expected):
|
||||||
ns2 = types.SimpleNamespace(x=1, y=2)
|
self.assertEqual(len(ns.__dict__), len(expected))
|
||||||
ns3 = types.SimpleNamespace(**dict(x=1, y=2))
|
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):
|
with self.assertRaises(TypeError):
|
||||||
types.SimpleNamespace(1, 2, 3)
|
types.SimpleNamespace([], []) # too many positional arguments
|
||||||
with self.assertRaises(TypeError):
|
with self.assertRaises(TypeError):
|
||||||
types.SimpleNamespace(**{1: 2})
|
types.SimpleNamespace(1) # not a mapping or iterable
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
self.assertEqual(len(ns1.__dict__), 0)
|
types.SimpleNamespace([1]) # non-iterable
|
||||||
self.assertEqual(vars(ns1), {})
|
with self.assertRaises(ValueError):
|
||||||
self.assertEqual(len(ns2.__dict__), 2)
|
types.SimpleNamespace([['x']]) # not a pair
|
||||||
self.assertEqual(vars(ns2), {'y': 2, 'x': 1})
|
with self.assertRaises(ValueError):
|
||||||
self.assertEqual(len(ns3.__dict__), 2)
|
types.SimpleNamespace([['x', 'y', 'z']])
|
||||||
self.assertEqual(vars(ns3), {'y': 2, 'x': 1})
|
with self.assertRaises(TypeError):
|
||||||
|
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
|
||||||
|
|
||||||
def test_unbound(self):
|
def test_unbound(self):
|
||||||
ns1 = vars(types.SimpleNamespace())
|
ns1 = vars(types.SimpleNamespace())
|
||||||
|
|
|
@ -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.
|
|
@ -43,10 +43,28 @@ namespace_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
|
||||||
static int
|
static int
|
||||||
namespace_init(_PyNamespaceObject *ns, PyObject *args, PyObject *kwds)
|
namespace_init(_PyNamespaceObject *ns, PyObject *args, PyObject *kwds)
|
||||||
{
|
{
|
||||||
if (PyTuple_GET_SIZE(args) != 0) {
|
PyObject *arg = NULL;
|
||||||
PyErr_Format(PyExc_TypeError, "no positional arguments expected");
|
if (!PyArg_UnpackTuple(args, _PyType_Name(Py_TYPE(ns)), 0, 1, &arg)) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
if (arg != NULL) {
|
||||||
|
PyObject *dict;
|
||||||
|
if (PyDict_CheckExact(arg)) {
|
||||||
|
dict = Py_NewRef(arg);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
dict = PyObject_CallOneArg((PyObject *)&PyDict_Type, arg);
|
||||||
|
if (dict == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int err = (!PyArg_ValidateKeywordArguments(dict) ||
|
||||||
|
PyDict_Update(ns->ns_dict, dict) < 0);
|
||||||
|
Py_DECREF(dict);
|
||||||
|
if (err) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (kwds == NULL) {
|
if (kwds == NULL) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -227,7 +245,7 @@ static PyMethodDef namespace_methods[] = {
|
||||||
|
|
||||||
|
|
||||||
PyDoc_STRVAR(namespace_doc,
|
PyDoc_STRVAR(namespace_doc,
|
||||||
"SimpleNamespace(**kwargs)\n\
|
"SimpleNamespace(mapping_or_iterable=(), /, **kwargs)\n\
|
||||||
--\n\n\
|
--\n\n\
|
||||||
A simple attribute-based namespace.");
|
A simple attribute-based namespace.");
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue