https://github.com/python/cpython/commit/646bd86e3b2f4f484129bd4a926cf73fafc9f874
commit: 646bd86e3b2f4f484129bd4a926cf73fafc9f874
branch: main
author: Victor Stinner <[email protected]>
committer: vstinner <[email protected]>
date: 2026-02-21T15:30:40Z
summary:

gh-141510: Fix copy.deepcopy() for recursive frozendict (#145027)

files:
M Lib/copy.py
M Lib/test/test_copy.py

diff --git a/Lib/copy.py b/Lib/copy.py
index 33dabb3395a7c0..6149301ad1389e 100644
--- a/Lib/copy.py
+++ b/Lib/copy.py
@@ -204,7 +204,17 @@ def _deepcopy_dict(x, memo, deepcopy=deepcopy):
 d[dict] = _deepcopy_dict
 
 def _deepcopy_frozendict(x, memo, deepcopy=deepcopy):
-    y = _deepcopy_dict(x, memo, deepcopy)
+    y = {}
+    for key, value in x.items():
+        y[deepcopy(key, memo)] = deepcopy(value, memo)
+
+    # We're not going to put the frozendict in the memo, but it's still
+    # important we check for it, in case the frozendict contains recursive
+    # mutable structures.
+    try:
+        return memo[id(x)]
+    except KeyError:
+        pass
     return frozendict(y)
 d[frozendict] = _deepcopy_frozendict
 
diff --git a/Lib/test/test_copy.py b/Lib/test/test_copy.py
index 858e5e089d5aba..98f56b5ae87f96 100644
--- a/Lib/test/test_copy.py
+++ b/Lib/test/test_copy.py
@@ -432,6 +432,23 @@ def test_deepcopy_frozendict(self):
         self.assertIsNot(x, y)
         self.assertIsNot(x["foo"], y["foo"])
 
+        # recursive frozendict
+        x = frozendict(foo=[])
+        x['foo'].append(x)
+        y = copy.deepcopy(x)
+        self.assertEqual(y.keys(), x.keys())
+        self.assertIsNot(x, y)
+        self.assertIsNot(x["foo"], y["foo"])
+        self.assertIs(y['foo'][0], y)
+
+        x = frozendict(foo=[])
+        x['foo'].append(x)
+        x = x['foo']
+        y = copy.deepcopy(x)
+        self.assertIsNot(x, y)
+        self.assertIsNot(x[0], y[0])
+        self.assertIs(y[0]['foo'], y)
+
     @support.skip_emscripten_stack_overflow()
     @support.skip_wasi_stack_overflow()
     def test_deepcopy_reflexive_dict(self):

_______________________________________________
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