Bug description
copy.deepcopy uses None as the memo-miss sentinel (memo.get(d, None)), so when an object's __deepcopy__ returns None, the memo hit is indistinguishable from a miss. The object gets deep-copied again on every subsequent reference instead of once.
This was introduced by gh-132657 / PR #138429 which replaced the old _nil = [] sentinel with None for free-threading performance.
importcopycall_count=0classC:
def__deepcopy__(self, memo):
globalcall_countcall_count+=1memo[id(self)] =NonereturnNoneobj=C()
copy.deepcopy([obj, obj, obj])
print(call_count) # 3, expected 1
CPython versions tested on
main
Operating systems tested on
macOS
Linked PRs
Bug description
copy.deepcopyusesNoneas the memo-miss sentinel (memo.get(d, None)), so when an object's__deepcopy__returnsNone, the memo hit is indistinguishable from a miss. The object gets deep-copied again on every subsequent reference instead of once.This was introduced by gh-132657 / PR #138429 which replaced the old
_nil = []sentinel withNonefor free-threading performance.CPython versions tested on
main
Operating systems tested on
macOS
Linked PRs