]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-141510: Optimize frozendict(frozendict) (#145592)
authorVictor Stinner <vstinner@python.org>
Mon, 9 Mar 2026 14:47:02 +0000 (15:47 +0100)
committerGitHub <noreply@github.com>
Mon, 9 Mar 2026 14:47:02 +0000 (15:47 +0100)
Return the same object unmodified if it's exactly the frozendict
type.

Optimize also PyFrozenDict_New(frozendict).

Lib/test/test_capi/test_dict.py
Lib/test/test_dict.py
Objects/dictobject.c

index 5bdf74ef73ab5444d1c43484de15410d91806e0f..cd46fea5476ca6d45dbb3850095d561d550b4467 100644 (file)
@@ -619,6 +619,16 @@ class CAPITest(unittest.TestCase):
         self.assertEqual(dct, frozendict(x=1, y=2))
         self.assertIs(type(dct), frozendict)
 
+        # PyFrozenDict_New(frozendict) returns the same object unmodified
+        fd = frozendict(a=1, b=2, c=3)
+        fd2 = frozendict_new(fd)
+        self.assertIs(fd2, fd)
+
+        fd = FrozenDictSubclass(a=1, b=2, c=3)
+        fd2 = frozendict_new(fd)
+        self.assertIsNot(fd2, fd)
+        self.assertEqual(fd2, fd)
+
         # PyFrozenDict_New(NULL) creates an empty dictionary
         dct = frozendict_new(NULL)
         self.assertEqual(dct, frozendict())
index 45448d1264a53e3fee643cbeb7db97c57c5ad425..b2f4363b23e74802714ff59a2497d4ce3803df0a 100644 (file)
@@ -1829,6 +1829,13 @@ class FrozenDictTests(unittest.TestCase):
         with self.assertRaises(TypeError):
             dict.__init__(d, x=1)
 
+        # Avoid copy if it's frozendict type
+        d2 = frozendict(d)
+        self.assertIs(d2, d)
+        d2 = FrozenDict(d)
+        self.assertIsNot(d2, d)
+        self.assertEqual(d2, d)
+
     def test_copy(self):
         d = frozendict(x=1, y=2)
         d2 = d.copy()
index 61fde37f8d4fff726fbb62e618d44a95d6756ce8..b5f2a682c549821672bb78acc7f084926378b4c4 100644 (file)
@@ -5169,15 +5169,47 @@ dict_vectorcall(PyObject *type, PyObject * const*args,
         return NULL;
     }
 
-    PyObject *self;
-    if (Py_Is((PyTypeObject*)type, &PyFrozenDict_Type)
-        || PyType_IsSubtype((PyTypeObject*)type, &PyFrozenDict_Type))
-    {
-        self = frozendict_new(_PyType_CAST(type), NULL, NULL);
+    PyObject *self = dict_new(_PyType_CAST(type), NULL, NULL);
+    if (self == NULL) {
+        return NULL;
     }
-    else {
-        self = dict_new(_PyType_CAST(type), NULL, NULL);
+    if (nargs == 1) {
+        if (dict_update_arg(self, args[0]) < 0) {
+            Py_DECREF(self);
+            return NULL;
+        }
+        args++;
     }
+    if (kwnames != NULL) {
+        for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(kwnames); i++) {
+            PyObject *key = PyTuple_GET_ITEM(kwnames, i);  // borrowed
+            if (PyDict_SetItem(self, key, args[i]) < 0) {
+                Py_DECREF(self);
+                return NULL;
+            }
+        }
+    }
+    return self;
+}
+
+static PyObject *
+frozendict_vectorcall(PyObject *type, PyObject * const*args,
+                      size_t nargsf, PyObject *kwnames)
+{
+    Py_ssize_t nargs = PyVectorcall_NARGS(nargsf);
+    if (!_PyArg_CheckPositional("frozendict", nargs, 0, 1)) {
+        return NULL;
+    }
+
+    if (nargs == 1 && kwnames == NULL
+        && PyFrozenDict_CheckExact(args[0])
+        && Py_Is((PyTypeObject*)type, &PyFrozenDict_Type))
+    {
+        // frozendict(frozendict) returns the same object unmodified
+        return Py_NewRef(args[0]);
+    }
+
+    PyObject *self = frozendict_new(_PyType_CAST(type), NULL, NULL);
     if (self == NULL) {
         return NULL;
     }
@@ -8171,6 +8203,11 @@ PyObject*
 PyFrozenDict_New(PyObject *iterable)
 {
     if (iterable != NULL) {
+        if (PyFrozenDict_CheckExact(iterable)) {
+            // PyFrozenDict_New(frozendict) returns the same object unmodified
+            return Py_NewRef(iterable);
+        }
+
         PyObject *args = PyTuple_Pack(1, iterable);
         if (args == NULL) {
             return NULL;
@@ -8228,6 +8265,6 @@ PyTypeObject PyFrozenDict_Type = {
     .tp_alloc = _PyType_AllocNoTrack,
     .tp_new = frozendict_new,
     .tp_free = PyObject_GC_Del,
-    .tp_vectorcall = dict_vectorcall,
+    .tp_vectorcall = frozendict_vectorcall,
     .tp_version_tag = _Py_TYPE_VERSION_FROZENDICT,
 };