copy

Assignment never copies — it gives the same object a second name. copy.copy makes a new outer container that still shares the items inside; copy.deepcopy copies everything, all the way down.

Data typesAll Python versionsLive demo
Import
import copy
from copy import deepcopy
Public API
copy, deepcopy, replace (3.13+), Error
Shallow
copy.copy(x): new container, same items — like list(x), x.copy(), x[:]
Deep
copy.deepcopy(x): new container, recursively copied items; shared and self references are preserved via a memo dict
Hooks
__copy__(self), __deepcopy__(self, memo), __replace__(self, **changes); otherwise the pickle protocol (__reduce_ex__)
Source
Lib/copy.py — pure Python

Demo

Live evaluation
A list of two lists, copied both ways. Then the ORIGINAL inner list gets one more item — see which copies notice.
Try:
Inputs
alist[int | float]first inner list
blist[int | float]second inner list
xintvalue to append
Code
import copy
original = [[1, 2], [3]]
shallow = copy.copy(original)
deep = copy.deepcopy(original)
original[0].append(9)
(original, shallow, deep)
Result
([[1, 2, 9], [3]], [[1, 2, 9], [3]], [[1, 2], [3]])

Appending 9 to original[0] changes the shallow copy too — ([[1, 2, 9], [3]], [[1, 2, 9], [3]], [[1, 2], [3]]) — because copy.copy built a new outer list holding the SAME inner lists. Only the deep copy has its own inner lists. With plain assignment even the outer list is shared: alias shows the appended 3, clone does not.

Members

Common patterns

Independent copy of nested data
Lists of lists, dicts of lists, JSON-like data: deepcopy before modifying.
import copy
snapshot = copy.deepcopy(state)
2D grid copy without deepcopy
For a list of flat lists, copying each row is enough (and faster).
grid_copy = [row[:] for row in grid]
Change one field of an immutable object (3.13+)
copy.replace works on named tuples, dataclasses and other classes with __replace__.
import copy
moved = copy.replace(point, x=point.x + 1)
Custom copy behaviour
Share a cache between copies but copy the data.
import copy
class Model:
    def __deepcopy__(self, memo):
        new = Model.__new__(Model)
        new.data = copy.deepcopy(self.data, memo)
        new.cache = self.cache  # shared on purpose
        return new

Examples

1. Assignment is not a copy
a = [1, 2] b = a b.append(3) a
Returns
[1, 2, 3]
2. Shallow copy shares inner lists
import copy a = [[1], [2]] b = copy.copy(a) b[0].append(99) a
Returns
[[1, 99], [2]]
3. Deep copy does not
import copy a = [[1], [2]] b = copy.deepcopy(a) b[0].append(99) a
Returns
[[1], [2]]
4. Immutable objects come back as is
import copy t = (1, 2) copy.copy(t) is t
Returns
True
5. replace() a named tuple field (3.13+)
import copy from collections import namedtuple Point = namedtuple('Point', 'x y') copy.replace(Point(1, 2), x=5)
Returns
Point(x=5, y=2)
6. Modules cannot be copied
import copy, math copy.copy(math)
Returns
TypeError: cannot pickle 'module' object

Pitfalls

1. Multiplying a list of lists
[[0] * 3] * 3 repeats ONE inner list three times — the classic shared-row bug. Build each row separately.
[[0] * 3] * 3
grid = [[0] * 3] * 3
grid[0][0] = 1
grid
[[1, 0, 0], [1, 0, 0], [1, 0, 0]]
comprehension
grid = [[0] * 3 for _ in range(3)]
grid[0][0] = 1
grid
[[1, 0, 0], [0, 0, 0], [0, 0, 0]]
2. Shallow-copying nested data
.copy(), list(), [:] and copy.copy only copy the outer container. Nested lists and dicts are still shared.
dict.copy()
settings = {'tags': ['a']}
backup = settings.copy()
settings['tags'].append('b')
backup
{'tags': ['a', 'b']}
deepcopy
import copy
settings = {'tags': ['a']}
backup = copy.deepcopy(settings)
settings['tags'].append('b')
backup
{'tags': ['a']}

When to use

Use it
  • Snapshots of nested, mutable state before changing it
  • Duplicating objects whose class you do not control
  • copy.replace: "the same, but with this field changed" for immutable records
Reach for something else
  • Flat lists and dicts → list.copy(), dict.copy() or a slice (same result, clearer)
  • Huge object graphs in hot loops → deepcopy is slow; restructure or copy only what changes
  • Objects holding files, sockets, locks → they cannot be copied

Notes

CPython impl
Lib/copy.py; types it does not special-case go through copyreg.dispatch_table and __reduce_ex__(4), the pickle protocol
Not copied
Functions, classes and other immutable atoms are returned unchanged; modules, generators, files and locks raise TypeError: cannot pickle ...
Versions
copy.replace and __replace__ added in 3.13

FAQ

A shallow copy (copy.copy, list.copy, dict.copy, slicing) creates a new outer container but puts the same inner objects in it. A deep copy (copy.deepcopy) also copies those inner objects, recursively, so nothing mutable is shared with the original.