Lib/copy.py (part 2)
Source:
cpython 3.14 @ ab2d84fe1023/Lib/copy.py
This annotation covers deepcopy internals. See lib_copy_detail for copy.copy, __copy__, dispatch tables, and shallow copying of containers.
Map
| Lines | Symbol | Role |
|---|---|---|
| 1-80 | deepcopy entry | Dispatch to atomic, memo hit, or _deepcopy_dispatch |
| 81-180 | _deepcopy_atomic | Return the same object (int, str, float, None, etc.) |
| 181-300 | _deepcopy_list / _deepcopy_tuple | Recursively copy container elements |
| 301-400 | _reconstruct / __reduce_ex__ | Copy arbitrary objects via pickle protocol |
Reading
deepcopy entry
# CPython: Lib/copy.py:128 deepcopy
def deepcopy(x, memo=None, _nil=[]):
"""Return a deep copy of x."""
if memo is None:
memo = {}
d = id(x)
y = memo.get(d, _nil)
if y is not _nil:
return y # Already copied (handles circular references)
cls = type(x)
copier = _deepcopy_dispatch.get(cls)
if copier is not None:
y = copier(x, memo)
elif (copier := _deepcopy_dispatch.get(type(x).__reduce_ex__)):
y = _reconstruct(x, memo, *x.__reduce_ex__(4))
else:
...
memo[d] = y
_keep_alive(x, memo) # Prevent x from being GC'd while memo holds id(x)
return y
The memo dict prevents infinite recursion for self-referential structures: lst = []; lst.append(lst); deepcopy(lst) returns a new list containing itself.
_deepcopy_atomic
# CPython: Lib/copy.py:230 _deepcopy_atomic
def _deepcopy_atomic(x, memo):
return x # These types are immutable: no need to copy
for t in (type(None), int, float, bool, complex, str,
tuple, bytes, frozenset, type, range, slice,
type(Ellipsis), type(NotImplemented),
types.BuiltinFunctionType, types.FunctionType):
_deepcopy_dispatch[t] = _deepcopy_atomic
Immutable objects are returned as-is. This is a significant optimization: deepcopy({'a': 1, 'b': 'hello'}) copies the dict but shares the int and str objects.
_deepcopy_list
# CPython: Lib/copy.py:250 _deepcopy_list
def _deepcopy_list(x, memo):
y = []
memo[id(x)] = y # Register BEFORE recursing to handle cycles
append = y.append
for a in x:
append(deepcopy(a, memo))
return y
The key insight: memo[id(x)] = y is done before recursing into elements. If an element references x, the memo lookup will find the (still empty) copy y instead of infinite recursion.
_reconstruct
# CPython: Lib/copy.py:300 _reconstruct
def _reconstruct(x, memo, func, args, state=None, listiter=None, dictiter=None,
*, deepcopy=deepcopy):
"""Reconstruct an object using its __reduce__ output."""
deep = memo is not None
if deep and args:
args = (deepcopy(arg, memo) for arg in args)
y = func(*args)
if state is not None:
if deep:
state = deepcopy(state, memo)
if hasattr(y, '__setstate__'):
y.__setstate__(state)
else:
y.__dict__.update(state)
...
return y
_reconstruct drives the pickle protocol: __reduce_ex__ returns (callable, args, state, ...). The callable is invoked to create the object, then state is applied via __setstate__. This handles complex objects like datetime, user-defined classes, etc.
gopy notes
deepcopy is module/copy.DeepCopy in module/copy/module.go. _deepcopy_dispatch is a Go map from reflect.Type to copy functions. Atomic types are returned directly. _reconstruct uses objects.ReduceEx to get the reduce tuple and objects.CallOneArg to invoke the constructor.