https://github.com/python/cpython/commit/16ccdbc50a3034f9c0e5ea6845356281b2ad04bd
commit: 16ccdbc50a3034f9c0e5ea6845356281b2ad04bd
branch: main
author: Victor Stinner <[email protected]>
committer: vstinner <[email protected]>
date: 2026-02-18T18:03:04+01:00
summary:

gh-141510: Fix frozendict.fromkeys() for dict subclasses (#144962)

Copy also the dictionary if a dict subclass returns a frozendict.

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

diff --git a/Lib/test/test_dict.py b/Lib/test/test_dict.py
index 1a8ae1cd42356e..71f72cb2557670 100644
--- a/Lib/test/test_dict.py
+++ b/Lib/test/test_dict.py
@@ -1815,6 +1815,16 @@ class FrozenDictSubclass2(frozendict):
         self.assertEqual(fd, frozendict(a=None, b=None, c=None))
         self.assertEqual(type(fd), FrozenDictSubclass2)
 
+        # Dict subclass which overrides the constructor
+        class DictSubclass(dict):
+            def __new__(self):
+                return created
+
+        fd = DictSubclass.fromkeys("abc")
+        self.assertEqual(fd, frozendict(x=1, a=None, b=None, c=None))
+        self.assertEqual(type(fd), DictSubclass)
+        self.assertEqual(created, frozendict(x=1))
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/Objects/dictobject.c b/Objects/dictobject.c
index 8d3c34f87e2afe..af3fcca7455470 100644
--- a/Objects/dictobject.c
+++ b/Objects/dictobject.c
@@ -138,6 +138,7 @@ As a consequence of this, split keys have a maximum size of 
16.
 // Forward declarations
 static PyObject* frozendict_new(PyTypeObject *type, PyObject *args,
                                 PyObject *kwds);
+static PyObject* dict_new(PyTypeObject *type, PyObject *args, PyObject *kwds);
 static int dict_merge(PyObject *a, PyObject *b, int override);
 
 
@@ -3305,15 +3306,18 @@ _PyDict_FromKeys(PyObject *cls, PyObject *iterable, 
PyObject *value)
         return NULL;
     }
 
-    // If cls is a frozendict subclass with overridden constructor,
+    // If cls is a dict or frozendict subclass with overridden constructor,
     // copy the frozendict.
     PyTypeObject *cls_type = _PyType_CAST(cls);
-    if (PyFrozenDict_Check(d)
-        && PyObject_IsSubclass(cls, (PyObject*)&PyFrozenDict_Type)
-        && cls_type->tp_new != frozendict_new)
-    {
+    if (PyFrozenDict_Check(d) && cls_type->tp_new != frozendict_new) {
         // Subclass-friendly copy
-        PyObject *copy = frozendict_new(cls_type, NULL, NULL);
+        PyObject *copy;
+        if (PyObject_IsSubclass(cls, (PyObject*)&PyFrozenDict_Type)) {
+            copy = frozendict_new(cls_type, NULL, NULL);
+        }
+        else {
+            copy = dict_new(cls_type, NULL, NULL);
+        }
         if (copy == NULL) {
             Py_DECREF(d);
             return NULL;

_______________________________________________
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