From: Victor Stinner Date: Sat, 21 Feb 2026 15:30:40 +0000 (+0100) Subject: gh-141510: Fix copy.deepcopy() for recursive frozendict (#145027) X-Git-Tag: v3.15.0a7~192 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=646bd86e3b2f4f484129bd4a926cf73fafc9f874;p=thirdparty%2FPython%2Fcpython.git gh-141510: Fix copy.deepcopy() for recursive frozendict (#145027) --- diff --git a/Lib/copy.py b/Lib/copy.py index 33dabb3395a7..6149301ad138 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 858e5e089d5a..98f56b5ae87f 100644 --- a/Lib/test/test_copy.py +++ b/Lib/test/test_copy.py @@ -432,6 +432,23 @@ class TestCopy(unittest.TestCase): 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):