Skip to content

Commit 17c0eb9

Browse files
committed
gh-154594: Fix deepcopy memo lookup when __deepcopy__ returns None
1 parent 6e98393 commit 17c0eb9

3 files changed

Lines changed: 20 additions & 2 deletions

File tree

Lib/copy.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ class Error(Exception):
5959

6060
__all__ = ["Error", "copy", "deepcopy", "replace"]
6161

62+
_MEMO_MISS = object()
63+
6264
def copy(x):
6365
"""Shallow copy operation on arbitrary Python objects.
6466
@@ -122,8 +124,8 @@ def deepcopy(x, memo=None):
122124
if memo is None:
123125
memo = {}
124126
else:
125-
y = memo.get(d, None)
126-
if y is not None:
127+
y = memo.get(d, _MEMO_MISS)
128+
if y is not _MEMO_MISS:
127129
return y
128130

129131
copier = _deepcopy_dispatch.get(cls)

Lib/test/test_copy.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -939,6 +939,19 @@ def m(self):
939939
self.assertIs(g.b.__self__, g)
940940
g.b()
941941

942+
def test_deepcopy_memo_none_result(self):
943+
# Objects whose deepcopy result is None must still be memoized.
944+
call_count = 0
945+
class C:
946+
def __deepcopy__(self, memo):
947+
nonlocal call_count
948+
call_count += 1
949+
memo[id(self)] = None
950+
return None
951+
obj = C()
952+
copy.deepcopy([obj, obj, obj])
953+
self.assertEqual(call_count, 1)
954+
942955

943956
class TestReplace(unittest.TestCase):
944957

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix :func:`copy.deepcopy` memo lookup using ``None`` as the miss sentinel,
2+
which prevented memoization of objects whose deep copy result is ``None``.
3+
Use a private sentinel object instead. Patch by tonghuaroot.

0 commit comments

Comments
 (0)