copy.deepcopy

Walks the whole object graph. A memo dict maps each original object to its copy, so an object referenced twice is copied once — and a structure that contains itself is copied without infinite recursion.

copy functionAll Python versionsLive demo
Common call
snapshot = copy.deepcopy(state)
Returns
an independent copy, all levels deep
Replaces
hand-written recursive copying
Watch out
Slow on big graphs; fails on files, locks, generators
copy.deepcopy(objobj — Anything copyable.type: object · required, memomemo — id(original) → copy for objects already copied in this pass. Leave it out; pass it on only inside your own __deepcopy__(self, memo).type: dict · default: None=None)
→ same type as obj

Demo

Live evaluation
The same experiment as on the copy.copy page, with deepcopy: change the copy, check the original.
Try:
Inputs
alist[int | float]first inner list
blist[int | float]second inner list
Code
import copy
original = [[1, 2], [3]]
d = copy.deepcopy(original)
d[0].append(0)
d.append([])
(original, d)
Result
([[1, 2], [3]], [[1, 2, 0], [3], []])

The original stays [[1, 2], [3]] whatever you do to the deep copy. The cycle demo prints [1, 2, [...]] — the [...] is how repr shows a list inside itself — and (True, False) confirms the copy points at itself, not at the original. In the shared demo appending 3 to d[0] shows up in d[1] as well, because the memo made both slots refer to the same new list, while outer is untouched.

Parameters

NameTypeRequiredDescription
objobjectyesAnything copyable.
memodictno (None)id(original) → copy for objects already copied in this pass. Leave it out; pass it on only inside your own __deepcopy__(self, memo).

Return value

same type as obj — A fully independent copy (immutable parts may be the same objects).

Common patterns

Snapshot / undo
Save a full independent state before an operation you may roll back.
import copy
history.append(copy.deepcopy(board))
apply_move(board, move)
__deepcopy__ that passes memo on
Copy components with deepcopy(component, memo) so shared references and cycles still work.
import copy
class Node:
    def __deepcopy__(self, memo):
        new = Node.__new__(Node)
        memo[id(self)] = new
        new.children = copy.deepcopy(self.children, memo)
        new.parent = copy.deepcopy(self.parent, memo)
        return new
Faster alternative for flat rows
A list of lists of numbers or strings only needs one level of copying.
rows_copy = [row.copy() for row in rows]

Examples

1. Nested lists are copied
import copy a = [[1], [2]] b = copy.deepcopy(a) b[0].append(99) (a, b)
Returns
([[1], [2]], [[1, 99], [2]])
2. Dicts of lists too
import copy cfg = {'tags': ['a']} new = copy.deepcopy(cfg) new['tags'].append('b') cfg
Returns
{'tags': ['a']}
3. Shared references stay shared
import copy inner = [1] d = copy.deepcopy([inner, inner]) d[0] is d[1]
Returns
True
4. Cycles are reproduced
import copy a = [1] a.append(a) b = copy.deepcopy(a) (b, b[1] is b)
Returns
([1, [...]], True)
5. Tuples of immutables are not copied
import copy t = (1, 2) copy.deepcopy(t) is t
Returns
True
6. Tuples with lists are
import copy t = (1, [2]) copy.deepcopy(t) is t
Returns
False
7. Locks cannot be copied
import copy, threading copy.deepcopy({'lock': threading.Lock()})
Returns
TypeError: cannot pickle '_thread.lock' object

Pitfalls

1. Using copy() for nested data
The single most common copy bug: a shallow copy of a list of lists shares the rows.
copy.copy
import copy
matrix = [[1, 2], [3, 4]]
m2 = copy.copy(matrix)
m2[1][1] = 0
matrix
[[1, 2], [3, 0]]
copy.deepcopy
import copy
matrix = [[1, 2], [3, 4]]
m2 = copy.deepcopy(matrix)
m2[1][1] = 0
matrix
[[1, 2], [3, 4]]
2. Deep-copying something that holds a resource
deepcopy follows every attribute. One lock, file or generator anywhere inside makes the whole copy fail — exclude it in __deepcopy__.
plain deepcopy
import copy, threading
class Service:
    def __init__(self):
        self.data = [1]
        self.lock = threading.Lock()
copy.deepcopy(Service())
TypeError: cannot pickle '_thread.lock' object
__deepcopy__
import copy, threading
class Service:
    def __init__(self):
        self.data = [1]
        self.lock = threading.Lock()
    def __deepcopy__(self, memo):
        new = Service()
        new.data = copy.deepcopy(self.data, memo)
        return new
copy.deepcopy(Service()).data
[1]

When to use

Use it
  • Nested mutable data that must be fully independent
  • Undo stacks, simulations, test fixtures built from a template
  • Graphs with shared nodes or cycles
Reach for something else
  • Flat containers → .copy() is enough and much faster
  • JSON-like data on a hot path → a hand-written copy of the parts you change
  • Objects with OS resources → write __deepcopy__ or copy the data only

Notes

CPython impl
copy.deepcopy in Lib/copy.py: memo check → _deepcopy_dispatch (list, dict, tuple, atomics) → __deepcopy__ → __reduce_ex__(4) + _reconstruct
Memo
memo maps id(original) → copy; lists and dicts are stored in it before their items are copied, which is what makes cycles work
Recursion
It recurses once per nesting level: a list nested 2000 levels deep raises RecursionError with the default recursion limit

FAQ

copy.deepcopy(d). dict.copy() and dict(d) are shallow: nested lists and dicts inside are still shared with the original.