summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSerhiy Storchaka <storchaka@gmail.com>2023-09-10 05:09:25 (GMT)
committerGitHub <noreply@github.com>2023-09-10 05:09:25 (GMT)
commit92578919a60ebe2b8d6d42377f1e27479c156d65 (patch)
tree39e90cc7a5130e034130e23ff01b34a855626411
parent0eab2427b149cd46e0dee3efbb6b2cfca2a4f723 (diff)
downloadcpython-92578919a60ebe2b8d6d42377f1e27479c156d65.zip
cpython-92578919a60ebe2b8d6d42377f1e27479c156d65.tar.gz
cpython-92578919a60ebe2b8d6d42377f1e27479c156d65.tar.bz2
gh-109174: Add support of SimpleNamespace in copy.replace() (GH-109175)
-rw-r--r--Doc/library/types.rst2
-rw-r--r--Lib/test/test_types.py27
-rw-r--r--Misc/NEWS.d/next/Library/2023-09-09-09-05-41.gh-issue-109174.OJea5s.rst1
-rw-r--r--Objects/namespaceobject.c28
4 files changed, 58 insertions, 0 deletions
diff --git a/Doc/library/types.rst b/Doc/library/types.rst
index 82300af..875916b 100644
--- a/Doc/library/types.rst
+++ b/Doc/library/types.rst
@@ -504,6 +504,8 @@ Additional Utility Classes and Functions
However, for a structured record type use :func:`~collections.namedtuple`
instead.
+ :class:`!SimpleNamespace` objects are supported by :func:`copy.replace`.
+
.. versionadded:: 3.3
.. versionchanged:: 3.9
diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py
index f2efee9..c6bff79 100644
--- a/Lib/test/test_types.py
+++ b/Lib/test/test_types.py
@@ -1900,6 +1900,33 @@ class SimpleNamespaceTests(unittest.TestCase):
self.assertEqual(ns, ns_roundtrip, pname)
+ def test_replace(self):
+ ns = types.SimpleNamespace(x=11, y=22)
+
+ ns2 = copy.replace(ns)
+ self.assertEqual(ns2, ns)
+ self.assertIsNot(ns2, ns)
+ self.assertIs(type(ns2), types.SimpleNamespace)
+ self.assertEqual(vars(ns2), {'x': 11, 'y': 22})
+ ns2.x = 3
+ self.assertEqual(ns.x, 11)
+ ns.x = 4
+ self.assertEqual(ns2.x, 3)
+
+ self.assertEqual(vars(copy.replace(ns, x=1)), {'x': 1, 'y': 22})
+ self.assertEqual(vars(copy.replace(ns, y=2)), {'x': 4, 'y': 2})
+ self.assertEqual(vars(copy.replace(ns, x=1, y=2)), {'x': 1, 'y': 2})
+
+ def test_replace_subclass(self):
+ class Spam(types.SimpleNamespace):
+ pass
+
+ spam = Spam(ham=8, eggs=9)
+ spam2 = copy.replace(spam, ham=5)
+
+ self.assertIs(type(spam2), Spam)
+ self.assertEqual(vars(spam2), {'ham': 5, 'eggs': 9})
+
def test_fake_namespace_compare(self):
# Issue #24257: Incorrect use of PyObject_IsInstance() caused
# SystemError.
diff --git a/Misc/NEWS.d/next/Library/2023-09-09-09-05-41.gh-issue-109174.OJea5s.rst b/Misc/NEWS.d/next/Library/2023-09-09-09-05-41.gh-issue-109174.OJea5s.rst
new file mode 100644
index 0000000..63461fa
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2023-09-09-09-05-41.gh-issue-109174.OJea5s.rst
@@ -0,0 +1 @@
+Add support of :class:`types.SimpleNamespace` in :func:`copy.replace`.
diff --git a/Objects/namespaceobject.c b/Objects/namespaceobject.c
index 11cf859..204c114 100644
--- a/Objects/namespaceobject.c
+++ b/Objects/namespaceobject.c
@@ -189,9 +189,37 @@ namespace_reduce(_PyNamespaceObject *ns, PyObject *Py_UNUSED(ignored))
}
+static PyObject *
+namespace_replace(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+ if (!_PyArg_NoPositional("__replace__", args)) {
+ return NULL;
+ }
+
+ PyObject *result = PyObject_CallNoArgs((PyObject *)Py_TYPE(self));
+ if (!result) {
+ return NULL;
+ }
+ if (PyDict_Update(((_PyNamespaceObject*)result)->ns_dict,
+ ((_PyNamespaceObject*)self)->ns_dict) < 0)
+ {
+ Py_DECREF(result);
+ return NULL;
+ }
+ if (kwargs) {
+ if (PyDict_Update(((_PyNamespaceObject*)result)->ns_dict, kwargs) < 0) {
+ Py_DECREF(result);
+ return NULL;
+ }
+ }
+ return result;
+}
+
+
static PyMethodDef namespace_methods[] = {
{"__reduce__", (PyCFunction)namespace_reduce, METH_NOARGS,
namespace_reduce__doc__},
+ {"__replace__", _PyCFunction_CAST(namespace_replace), METH_VARARGS|METH_KEYWORDS, NULL},
{NULL, NULL} // sentinel
};