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.
import copy
call_count = 0
class C:
def __deepcopy__(self, memo):
global call_count
call_count += 1
memo[id(self)] = None
return None
obj = C()
copy.deepcopy([obj, obj, obj])
print(call_count) # 3, expected 1
CPython versions tested on
main
Operating systems tested on
macOS
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