Skip to content

Commit 646bd86

Browse files
authored
gh-141510: Fix copy.deepcopy() for recursive frozendict (#145027)
1 parent 7258dbc commit 646bd86

File tree

2 files changed

+28
-1
lines changed

2 files changed

+28
-1
lines changed

Lib/copy.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,17 @@ def _deepcopy_dict(x, memo, deepcopy=deepcopy):
204204
d[dict] = _deepcopy_dict
205205

206206
def _deepcopy_frozendict(x, memo, deepcopy=deepcopy):
207-
y = _deepcopy_dict(x, memo, deepcopy)
207+
y = {}
208+
for key, value in x.items():
209+
y[deepcopy(key, memo)] = deepcopy(value, memo)
210+
211+
# We're not going to put the frozendict in the memo, but it's still
212+
# important we check for it, in case the frozendict contains recursive
213+
# mutable structures.
214+
try:
215+
return memo[id(x)]
216+
except KeyError:
217+
pass
208218
return frozendict(y)
209219
d[frozendict] = _deepcopy_frozendict
210220

Lib/test/test_copy.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,23 @@ def test_deepcopy_frozendict(self):
432432
self.assertIsNot(x, y)
433433
self.assertIsNot(x["foo"], y["foo"])
434434

435+
# recursive frozendict
436+
x = frozendict(foo=[])
437+
x['foo'].append(x)
438+
y = copy.deepcopy(x)
439+
self.assertEqual(y.keys(), x.keys())
440+
self.assertIsNot(x, y)
441+
self.assertIsNot(x["foo"], y["foo"])
442+
self.assertIs(y['foo'][0], y)
443+
444+
x = frozendict(foo=[])
445+
x['foo'].append(x)
446+
x = x['foo']
447+
y = copy.deepcopy(x)
448+
self.assertIsNot(x, y)
449+
self.assertIsNot(x[0], y[0])
450+
self.assertIs(y[0]['foo'], y)
451+
435452
@support.skip_emscripten_stack_overflow()
436453
@support.skip_wasi_stack_overflow()
437454
def test_deepcopy_reflexive_dict(self):

0 commit comments

Comments
 (0)