https://github.com/python/cpython/commit/0dfe649400a0b67318169ec813475f4949ad7b69
commit: 0dfe649400a0b67318169ec813475f4949ad7b69
branch: main
author: Victor Stinner <[email protected]>
committer: vstinner <[email protected]>
date: 2026-03-09T15:47:02+01:00
summary:

gh-141510: Optimize frozendict(frozendict) (#145592)

Return the same object unmodified if it's exactly the frozendict
type.

Optimize also PyFrozenDict_New(frozendict).

files:
M Lib/test/test_capi/test_dict.py
M Lib/test/test_dict.py
M Objects/dictobject.c

diff --git a/Lib/test/test_capi/test_dict.py b/Lib/test/test_capi/test_dict.py
index 5bdf74ef73ab54..cd46fea5476ca6 100644
--- a/Lib/test/test_capi/test_dict.py
+++ b/Lib/test/test_capi/test_dict.py
@@ -619,6 +619,16 @@ def test_frozendict_new(self):
         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())
diff --git a/Lib/test/test_dict.py b/Lib/test/test_dict.py
index 45448d1264a53e..b2f4363b23e748 100644
--- a/Lib/test/test_dict.py
+++ b/Lib/test/test_dict.py
@@ -1829,6 +1829,13 @@ def test_constructor(self):
         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()
diff --git a/Objects/dictobject.c b/Objects/dictobject.c
index 61fde37f8d4fff..b5f2a682c54982 100644
--- a/Objects/dictobject.c
+++ b/Objects/dictobject.c
@@ -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,
 };

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to