https://github.com/python/cpython/commit/95f56b120623f17990be81379661de270e95e3c5
commit: 95f56b120623f17990be81379661de270e95e3c5
branch: main
author: Victor Stinner <[email protected]>
committer: vstinner <[email protected]>
date: 2026-03-04T19:11:00Z
summary:

gh-141510: Return frozendict unmodified in PyDict_Copy() (#145505)

Add also the internal _PyDict_CopyAsDict() function.

files:
M Include/internal/pycore_dict.h
M Lib/test/test_capi/test_dict.py
M Objects/dictobject.c
M Objects/typeobject.c

diff --git a/Include/internal/pycore_dict.h b/Include/internal/pycore_dict.h
index 59e88be6aeec12..1aeec32f55a7f3 100644
--- a/Include/internal/pycore_dict.h
+++ b/Include/internal/pycore_dict.h
@@ -160,6 +160,8 @@ extern void _PyDict_Clear_LockHeld(PyObject *op);
 PyAPI_FUNC(void) _PyDict_EnsureSharedOnRead(PyDictObject *mp);
 #endif
 
+extern PyObject* _PyDict_CopyAsDict(PyObject *op);
+
 #define DKIX_EMPTY (-1)
 #define DKIX_DUMMY (-2)  /* Used internally */
 #define DKIX_ERROR (-3)
diff --git a/Lib/test/test_capi/test_dict.py b/Lib/test/test_capi/test_dict.py
index d9de9bb4c8125d..561e1ea4d52846 100644
--- a/Lib/test/test_capi/test_dict.py
+++ b/Lib/test/test_capi/test_dict.py
@@ -98,14 +98,18 @@ def test_dict_copy(self):
         # Test PyDict_Copy()
         copy = _testlimitedcapi.dict_copy
         for dict_type in ANYDICT_TYPES:
-            if issubclass(dict_type, frozendict):
-                expected_type = frozendict
-            else:
-                expected_type = dict
             dct = dict_type({1: 2})
             dct_copy = copy(dct)
-            self.assertIs(type(dct_copy), expected_type)
-            self.assertEqual(dct_copy, dct)
+            if dict_type == frozendict:
+                expected_type = frozendict
+                self.assertIs(dct_copy, dct)
+            else:
+                if issubclass(dict_type, frozendict):
+                    expected_type = frozendict
+                else:
+                    expected_type = dict
+                self.assertIs(type(dct_copy), expected_type)
+                self.assertEqual(dct_copy, dct)
 
         for test_type in NOT_ANYDICT_TYPES + OTHER_TYPES:
             self.assertRaises(SystemError, copy, test_type())
diff --git a/Objects/dictobject.c b/Objects/dictobject.c
index 5894fdb614ebdc..0f123c3c2bb994 100644
--- a/Objects/dictobject.c
+++ b/Objects/dictobject.c
@@ -4235,9 +4235,6 @@ static PyObject *
 dict_copy_impl(PyDictObject *self)
 /*[clinic end generated code: output=ffb782cf970a5c39 input=73935f042b639de4]*/
 {
-    if (PyFrozenDict_CheckExact(self)) {
-        return Py_NewRef(self);
-    }
     return PyDict_Copy((PyObject *)self);
 }
 
@@ -4263,18 +4260,17 @@ copy_values(PyDictValues *values)
 }
 
 static PyObject *
-copy_lock_held(PyObject *o)
+copy_lock_held(PyObject *o, int as_frozendict)
 {
     PyObject *copy;
     PyDictObject *mp;
-    int frozendict = PyFrozenDict_Check(o);
 
     ASSERT_DICT_LOCKED(o);
 
     mp = (PyDictObject *)o;
     if (mp->ma_used == 0) {
         /* The dict is empty; just return a new dict. */
-        if (frozendict) {
+        if (as_frozendict) {
             return PyFrozenDict_New(NULL);
         }
         else {
@@ -4288,7 +4284,7 @@ copy_lock_held(PyObject *o)
         if (newvalues == NULL) {
             return PyErr_NoMemory();
         }
-        if (frozendict) {
+        if (as_frozendict) {
             split_copy = (PyDictObject *)PyObject_GC_New(PyFrozenDictObject,
                                                          &PyFrozenDict_Type);
         }
@@ -4307,7 +4303,7 @@ copy_lock_held(PyObject *o)
         split_copy->ma_used = mp->ma_used;
         split_copy->_ma_watcher_tag = 0;
         dictkeys_incref(mp->ma_keys);
-        if (frozendict) {
+        if (as_frozendict) {
             PyFrozenDictObject *frozen = (PyFrozenDictObject *)split_copy;
             frozen->ma_hash = -1;
         }
@@ -4318,7 +4314,7 @@ copy_lock_held(PyObject *o)
     if (Py_TYPE(mp)->tp_iter == dict_iter &&
             mp->ma_values == NULL &&
             (mp->ma_used >= (mp->ma_keys->dk_nentries * 2) / 3) &&
-            !frozendict)
+            !as_frozendict)
     {
         /* Use fast-copy if:
 
@@ -4350,7 +4346,7 @@ copy_lock_held(PyObject *o)
         return (PyObject *)new;
     }
 
-    if (frozendict) {
+    if (as_frozendict) {
         copy = PyFrozenDict_New(NULL);
     }
     else {
@@ -4364,6 +4360,19 @@ copy_lock_held(PyObject *o)
     return NULL;
 }
 
+// Similar to PyDict_Copy(), but copy also frozendict.
+static PyObject *
+_PyDict_Copy(PyObject *o)
+{
+    assert(PyAnyDict_Check(o));
+
+    PyObject *res;
+    Py_BEGIN_CRITICAL_SECTION(o);
+    res = copy_lock_held(o, PyFrozenDict_Check(o));
+    Py_END_CRITICAL_SECTION();
+    return res;
+}
+
 PyObject *
 PyDict_Copy(PyObject *o)
 {
@@ -4372,11 +4381,22 @@ PyDict_Copy(PyObject *o)
         return NULL;
     }
 
-    PyObject *res;
-    Py_BEGIN_CRITICAL_SECTION(o);
+    if (PyFrozenDict_CheckExact(o)) {
+        return Py_NewRef(o);
+    }
+
+    return _PyDict_Copy(o);
+}
 
-    res = copy_lock_held(o);
+// Similar to PyDict_Copy(), but return a dict if the argument is a frozendict.
+PyObject*
+_PyDict_CopyAsDict(PyObject *o)
+{
+    assert(PyAnyDict_Check(o));
 
+    PyObject *res;
+    Py_BEGIN_CRITICAL_SECTION(o);
+    res = copy_lock_held(o, 0);
     Py_END_CRITICAL_SECTION();
     return res;
 }
@@ -4925,7 +4945,7 @@ dict_or(PyObject *self, PyObject *other)
     if (!PyAnyDict_Check(self) || !PyAnyDict_Check(other)) {
         Py_RETURN_NOTIMPLEMENTED;
     }
-    PyObject *new = PyDict_Copy(self);
+    PyObject *new = _PyDict_Copy(self);
     if (new == NULL) {
         return NULL;
     }
@@ -6479,7 +6499,7 @@ dictitems_xor_lock_held(PyObject *d1, PyObject *d2)
     ASSERT_DICT_LOCKED(d1);
     ASSERT_DICT_LOCKED(d2);
 
-    PyObject *temp_dict = copy_lock_held(d1);
+    PyObject *temp_dict = copy_lock_held(d1, PyFrozenDict_Check(d1));
     if (temp_dict == NULL) {
         return NULL;
     }
diff --git a/Objects/typeobject.c b/Objects/typeobject.c
index c5e94a8668fef2..1fdd3cbdaaa639 100644
--- a/Objects/typeobject.c
+++ b/Objects/typeobject.c
@@ -4850,21 +4850,9 @@ type_new_get_slots(type_new_ctx *ctx, PyObject *dict)
 static PyTypeObject*
 type_new_init(type_new_ctx *ctx)
 {
-    PyObject *dict;
-    if (PyFrozenDict_Check(ctx->orig_dict)) {
-        dict = PyDict_New();
-        if (dict == NULL) {
-            goto error;
-        }
-        if (PyDict_Merge(dict, ctx->orig_dict, 1) < 0) {
-            goto error;
-        }
-    }
-    else {
-        dict = PyDict_Copy(ctx->orig_dict);
-        if (dict == NULL) {
-            goto error;
-        }
+    PyObject *dict = _PyDict_CopyAsDict(ctx->orig_dict);
+    if (dict == NULL) {
+        goto error;
     }
 
     if (type_new_get_slots(ctx, dict) < 0) {

_______________________________________________
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