diff --git a/Doc/c-api/concrete.rst b/Doc/c-api/concrete.rst
index 84224dcca523b9..6bd4f030c55426 100644
--- a/Doc/c-api/concrete.rst
+++ b/Doc/c-api/concrete.rst
@@ -83,6 +83,7 @@ Container Objects
dict.rst
set.rst
+ simplenamespace.rst
.. _otherobjects:
diff --git a/Doc/c-api/simplenamespace.rst b/Doc/c-api/simplenamespace.rst
new file mode 100644
index 00000000000000..c2665899c89201
--- /dev/null
+++ b/Doc/c-api/simplenamespace.rst
@@ -0,0 +1,15 @@
+.. highlight:: c
+
+SimpleNamespace Objects
+-----------------------
+
+.. c:function:: PyObject* PySimpleNamespace_New(PyObject *attrs)
+
+ Create a new simple namespace object.
+
+ *attrs* must be a mapping object with non-empty string keys.
+ It can be ``NULL``.
+
+ Return ``NULL`` and raise an exception on failure.
+
+ .. versionadded:: 3.11
diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst
index 734cf1572fcba4..41fec1e3cd5543 100644
--- a/Doc/whatsnew/3.11.rst
+++ b/Doc/whatsnew/3.11.rst
@@ -476,6 +476,10 @@ New Features
* Add a new :c:func:`PyType_GetQualName` function to get type's qualified name.
(Contributed by Hai Shi in :issue:`42035`.)
+* Add a new :c:func:`PySimpleNamespace_New` function to create a simple
+ namespace: C API of the :class:`types.SimpleNamespace` type.
+ (Contributed by Victor Stinner in :issue:`45482`.)
+
Porting to Python 3.11
----------------------
diff --git a/Include/Python.h b/Include/Python.h
index 89f60fe5c9f945..17886359b77e2f 100644
--- a/Include/Python.h
+++ b/Include/Python.h
@@ -76,7 +76,7 @@
#include "warnings.h"
#include "weakrefobject.h"
#include "structseq.h"
-#include "namespaceobject.h"
+#include "cpython/namespaceobject.h"
#include "cpython/picklebufobject.h"
#include "cpython/pytime.h"
#include "codecs.h"
diff --git a/Include/cpython/namespaceobject.h b/Include/cpython/namespaceobject.h
new file mode 100644
index 00000000000000..e8ffbe53b60248
--- /dev/null
+++ b/Include/cpython/namespaceobject.h
@@ -0,0 +1,18 @@
+// Simple namespace object interface
+
+#ifndef Py_LIMITED_API
+#ifndef Py_NAMESPACEOBJECT_H
+#define Py_NAMESPACEOBJECT_H
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+PyAPI_DATA(PyTypeObject) _PySimpleNamespace_Type;
+
+PyAPI_FUNC(PyObject *) PySimpleNamespace_New(PyObject *attrs);
+
+#ifdef __cplusplus
+}
+#endif
+#endif // !Py_NAMESPACEOBJECT_H
+#endif // !Py_LIMITED_API
diff --git a/Include/namespaceobject.h b/Include/namespaceobject.h
deleted file mode 100644
index 0c8d95c0f137c6..00000000000000
--- a/Include/namespaceobject.h
+++ /dev/null
@@ -1,19 +0,0 @@
-
-/* simple namespace object interface */
-
-#ifndef NAMESPACEOBJECT_H
-#define NAMESPACEOBJECT_H
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#ifndef Py_LIMITED_API
-PyAPI_DATA(PyTypeObject) _PyNamespace_Type;
-
-PyAPI_FUNC(PyObject *) _PyNamespace_New(PyObject *kwds);
-#endif /* !Py_LIMITED_API */
-
-#ifdef __cplusplus
-}
-#endif
-#endif /* !NAMESPACEOBJECT_H */
diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py
index b1218abc8af5ec..313a368da86a36 100644
--- a/Lib/test/test_types.py
+++ b/Lib/test/test_types.py
@@ -1,18 +1,20 @@
# Python test set -- part 6, built-in types
-from test.support import run_with_locale, cpython_only
import collections.abc
-from collections import namedtuple
import copy
import gc
import inspect
-import pickle
import locale
+import pickle
import sys
import types
+import typing
import unittest.mock
import weakref
-import typing
+
+from collections import namedtuple
+from test.support import run_with_locale, cpython_only
+from test.support import import_helper
T = typing.TypeVar("T")
@@ -1733,6 +1735,45 @@ class FakeSimpleNamespace(str):
with self.assertRaises(TypeError):
types.SimpleNamespace() >= FakeSimpleNamespace()
+ def test_capi(self):
+ _testcapi = import_helper.import_module('_testcapi')
+ PySimpleNamespace_New = _testcapi.PySimpleNamespace_New
+
+ def list_attributes(obj):
+ return [name for name in dir(obj) if not name.startswith('_')]
+
+ # two attributes
+ value1 = 'value'
+ value2 = 2
+ ns = PySimpleNamespace_New({'attr1': value1, 'attr2': value2})
+ self.assertIsInstance(ns, types.SimpleNamespace)
+ self.assertEqual(list_attributes(ns), ['attr1', 'attr2'])
+ self.assertIs(ns.attr1, value1)
+ self.assertIs(ns.attr2, value2)
+
+ # it's possible to modify a namespace
+ ns.attr1 = 1
+ self.assertEqual(ns.attr1, 1)
+ ns.attr3 = 3
+ self.assertEqual(list_attributes(ns), ['attr1', 'attr2', 'attr3'])
+
+ # empty namespace
+ ns = PySimpleNamespace_New({})
+ self.assertIsInstance(ns, types.SimpleNamespace)
+ self.assertEqual(list_attributes(ns), [])
+
+ # invalid arguments
+ for invalid_arg in (
+ 123,
+ 'string',
+ [('key', 'value')],
+ # non-string key
+ {123: 'value'},
+ ):
+ with (self.subTest(invalid_arg=invalid_arg),
+ self.assertRaises(TypeError)):
+ PySimpleNamespace_New(invalid_arg)
+
class CoroutineTests(unittest.TestCase):
def test_wrong_args(self):
diff --git a/Makefile.pre.in b/Makefile.pre.in
index 32bbab068f7027..403e60bc827e57 100644
--- a/Makefile.pre.in
+++ b/Makefile.pre.in
@@ -1157,7 +1157,6 @@ PYTHON_HEADERS= \
$(srcdir)/Include/methodobject.h \
$(srcdir)/Include/modsupport.h \
$(srcdir)/Include/moduleobject.h \
- $(srcdir)/Include/namespaceobject.h \
$(srcdir)/Include/object.h \
$(srcdir)/Include/objimpl.h \
$(srcdir)/Include/opcode.h \
@@ -1215,6 +1214,7 @@ PYTHON_HEADERS= \
$(srcdir)/Include/cpython/interpreteridobject.h \
$(srcdir)/Include/cpython/listobject.h \
$(srcdir)/Include/cpython/methodobject.h \
+ $(srcdir)/Include/cpython/namespaceobject.h \
$(srcdir)/Include/cpython/object.h \
$(srcdir)/Include/cpython/objimpl.h \
$(srcdir)/Include/cpython/odictobject.h \
diff --git a/Misc/NEWS.d/next/C API/2021-10-15-10-01-31.bpo-45482.69r5QI.rst b/Misc/NEWS.d/next/C API/2021-10-15-10-01-31.bpo-45482.69r5QI.rst
new file mode 100644
index 00000000000000..c4e70cfe0e5a67
--- /dev/null
+++ b/Misc/NEWS.d/next/C API/2021-10-15-10-01-31.bpo-45482.69r5QI.rst
@@ -0,0 +1,3 @@
+Add a new :c:func:`PySimpleNamespace_New` function to create a simple
+namespace: C API of the :class:`types.SimpleNamespace` type. Patch by Victor
+Stinner.
diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c
index 7cbd2dc3b6a51f..a24c6ca3354a0a 100644
--- a/Modules/_testcapimodule.c
+++ b/Modules/_testcapimodule.c
@@ -1167,7 +1167,7 @@ test_get_type_qualname(PyObject *self, PyObject *Py_UNUSED(ignored))
assert(strcmp(PyUnicode_AsUTF8(tp_qualname), "int") == 0);
Py_DECREF(tp_qualname);
- tp_qualname = PyType_GetQualName(&_PyNamespace_Type);
+ tp_qualname = PyType_GetQualName(&_PySimpleNamespace_Type);
assert(strcmp(PyUnicode_AsUTF8(tp_qualname), "SimpleNamespace") == 0);
Py_DECREF(tp_qualname);
@@ -5674,6 +5674,18 @@ type_get_version(PyObject *self, PyObject *type)
}
+static PyObject *
+test_simplenamespace(PyObject *self, PyObject *args)
+{
+ PyObject *items = NULL;
+ if (!PyArg_ParseTuple(args, "|O", &items)) {
+ return NULL;
+ }
+
+ return PySimpleNamespace_New(items);
+}
+
+
static PyObject *test_buildvalue_issue38913(PyObject *, PyObject *);
static PyObject *getargs_s_hash_int(PyObject *, PyObject *, PyObject*);
@@ -5957,6 +5969,7 @@ static PyMethodDef TestMethods[] = {
{"fatal_error", test_fatal_error, METH_VARARGS,
PyDoc_STR("fatal_error(message, release_gil=False): call Py_FatalError(message)")},
{"type_get_version", type_get_version, METH_O, PyDoc_STR("type->tp_version_tag")},
+ {"PySimpleNamespace_New", test_simplenamespace, METH_VARARGS, NULL},
{NULL, NULL} /* sentinel */
};
diff --git a/Modules/_testmultiphase.c b/Modules/_testmultiphase.c
index e0ed77d265cdcc..9bc9f320e42551 100644
--- a/Modules/_testmultiphase.c
+++ b/Modules/_testmultiphase.c
@@ -459,7 +459,7 @@ createfunc_nonmodule(PyObject *spec, PyModuleDef *def)
PyDict_SetItemString(dct, "three", three);
Py_DECREF(three);
- ns = _PyNamespace_New(dct);
+ ns = PySimpleNamespace_New(dct);
Py_DECREF(dct);
return ns;
}
diff --git a/Modules/timemodule.c b/Modules/timemodule.c
index ca8d62d5cc9380..ac495176438929 100644
--- a/Modules/timemodule.c
+++ b/Modules/timemodule.c
@@ -1653,7 +1653,7 @@ time_get_clock_info(PyObject *self, PyObject *args)
}
Py_CLEAR(obj);
- ns = _PyNamespace_New(dict);
+ ns = PySimpleNamespace_New(dict);
Py_DECREF(dict);
return ns;
diff --git a/Objects/namespaceobject.c b/Objects/namespaceobject.c
index fa37ed250d30a4..c8e5e72ab7c0ab 100644
--- a/Objects/namespaceobject.c
+++ b/Objects/namespaceobject.c
@@ -72,8 +72,12 @@ namespace_repr(PyObject *ns)
PyObject *separator, *pairsrepr, *repr = NULL;
const char * name;
- name = Py_IS_TYPE(ns, &_PyNamespace_Type) ? "namespace"
- : Py_TYPE(ns)->tp_name;
+ if (Py_IS_TYPE(ns, &_PySimpleNamespace_Type)) {
+ name = "namespace";
+ }
+ else {
+ name = Py_TYPE(ns)->tp_name;
+ }
i = Py_ReprEnter(ns);
if (i != 0) {
@@ -163,8 +167,8 @@ namespace_clear(_PyNamespaceObject *ns)
static PyObject *
namespace_richcompare(PyObject *self, PyObject *other, int op)
{
- if (PyObject_TypeCheck(self, &_PyNamespace_Type) &&
- PyObject_TypeCheck(other, &_PyNamespace_Type))
+ if (PyObject_TypeCheck(self, &_PySimpleNamespace_Type) &&
+ PyObject_TypeCheck(other, &_PySimpleNamespace_Type))
return PyObject_RichCompare(((_PyNamespaceObject *)self)->ns_dict,
((_PyNamespaceObject *)other)->ns_dict, op);
Py_RETURN_NOTIMPLEMENTED;
@@ -199,7 +203,7 @@ PyDoc_STRVAR(namespace_doc,
\n\
SimpleNamespace(**kwargs)");
-PyTypeObject _PyNamespace_Type = {
+PyTypeObject _PySimpleNamespace_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"types.SimpleNamespace", /* tp_name */
sizeof(_PyNamespaceObject), /* tp_basicsize */
@@ -244,18 +248,32 @@ PyTypeObject _PyNamespace_Type = {
PyObject *
-_PyNamespace_New(PyObject *kwds)
+PySimpleNamespace_New(PyObject *attrs)
{
- PyObject *ns = namespace_new(&_PyNamespace_Type, NULL, NULL);
- if (ns == NULL)
+ if (attrs != NULL && !PyDict_Check(attrs)) {
+ PyErr_Format(PyExc_TypeError, "expected dict, got %s",
+ Py_TYPE(attrs)->tp_name);
return NULL;
+ }
- if (kwds == NULL)
- return ns;
- if (PyDict_Update(((_PyNamespaceObject *)ns)->ns_dict, kwds) != 0) {
- Py_DECREF(ns);
+ PyObject *ns = namespace_new(&_PySimpleNamespace_Type, NULL, NULL);
+ if (ns == NULL) {
return NULL;
}
- return (PyObject *)ns;
+ if (attrs == NULL) {
+ return ns;
+ }
+
+ if (!PyArg_ValidateKeywordArguments(attrs)) {
+ goto error;
+ }
+ if (PyDict_Update(((_PyNamespaceObject *)ns)->ns_dict, attrs) < 0) {
+ goto error;
+ }
+ return ns;
+
+error:
+ Py_DECREF(ns);
+ return NULL;
}
diff --git a/Objects/object.c b/Objects/object.c
index 589dd6647318da..6268b77419ac9a 100644
--- a/Objects/object.c
+++ b/Objects/object.c
@@ -1947,7 +1947,7 @@ _PyTypes_Init(void)
INIT_TYPE(_PyInterpreterID_Type);
INIT_TYPE(_PyManagedBuffer_Type);
INIT_TYPE(_PyMethodWrapper_Type);
- INIT_TYPE(_PyNamespace_Type);
+ INIT_TYPE(_PySimpleNamespace_Type);
INIT_TYPE(_PyNone_Type);
INIT_TYPE(_PyNotImplemented_Type);
INIT_TYPE(_PyWeakref_CallableProxyType);
diff --git a/PCbuild/pythoncore.vcxproj b/PCbuild/pythoncore.vcxproj
index 877064e877debe..ede9eb1118be8e 100644
--- a/PCbuild/pythoncore.vcxproj
+++ b/PCbuild/pythoncore.vcxproj
@@ -140,6 +140,7 @@
+
@@ -231,7 +232,6 @@
-
diff --git a/PCbuild/pythoncore.vcxproj.filters b/PCbuild/pythoncore.vcxproj.filters
index b8841c90cc1b96..0812c02b203db1 100644
--- a/PCbuild/pythoncore.vcxproj.filters
+++ b/PCbuild/pythoncore.vcxproj.filters
@@ -342,9 +342,6 @@
Include
-
- Include
-
Include
@@ -408,12 +405,15 @@
Include\cpython
-
- Include\cpython
+
+ Include
Include\cpython
+
+ Include\cpython
+
Include
diff --git a/Python/import.c b/Python/import.c
index f2160928c4fa7c..45fc9071e0c6df 100644
--- a/Python/import.c
+++ b/Python/import.c
@@ -2479,7 +2479,7 @@ _PyImport_BootstrapImp(PyThreadState *tstate)
if (attrs == NULL) {
goto error;
}
- PyObject *spec = _PyNamespace_New(attrs);
+ PyObject *spec = PySimpleNamespace_New(attrs);
Py_DECREF(attrs);
if (spec == NULL) {
goto error;
diff --git a/Python/sysmodule.c b/Python/sysmodule.c
index ae3cbf1954e09e..5eccf3386587ac 100644
--- a/Python/sysmodule.c
+++ b/Python/sysmodule.c
@@ -26,7 +26,7 @@ Data members:
#include "pycore_pylifecycle.h" // _PyErr_WriteUnraisableDefaultHook()
#include "pycore_pymem.h" // _PyMem_SetDefaultAllocator()
#include "pycore_pystate.h" // _PyThreadState_GET()
-#include "pycore_structseq.h" // PyStructSequence_InitType()
+#include "pycore_structseq.h" // _PyStructSequence_InitType()
#include "pycore_tuple.h" // _PyTuple_FromArray()
#include "code.h"
@@ -2717,7 +2717,7 @@ make_impl_info(PyObject *version_info)
/* dict ready */
- ns = _PyNamespace_New(impl_info);
+ ns = PySimpleNamespace_New(impl_info);
Py_DECREF(impl_info);
return ns;