python copy.deepcopy: When and How to Use It
python copy.deepcopy: Learn how Python's copy.deepcopy creates fully independent object copies, how its memoization works, and when deep copying is the right choice.
python copy.deepcopy requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.
When you assign one object to another variable in Python, you copy the reference, not the value. For mutable objects like lists and dictionaries, this means two names can point to the same underlying data. The copy module provides copy.deepcopy to create a fully independent copy where no nested object is shared.
import copy original = {"items": [1, 2, {"nested": True}]} duplicate = copy.deepcopy(original) duplicate["items"][2]["nested"] = False print(original["items"][2]["nested"]) # True
The copied dictionary shares no mutable state with the original. Changes at any level of the copy leave the original untouched.
How deepcopy Traverses an Object Graph
deepcopy walks the entire object graph recursively. For each object it encounters, it creates a new object and then recursively copies every attribute, list item, dictionary key, and dictionary value. The result is a complete structural duplicate rather than a new outer container pointing at the same inner objects.
The function keeps an internal memo dictionary that maps already-copied objects to their copies. This memo serves two purposes:
- It prevents infinite recursion when the same object appears multiple times in the graph.
- It preserves shared references within the original structure.
import copy shared = [1, 2, 3] data = {"a": shared, "b": shared} result = copy.deepcopy(data) print(result["a"] is result["b"]) # True
Because the memo records that shared was already copied, both keys in the result point to the same new list. Without this behavior, deepcopy would either recurse forever on circular references or produce two unrelated copies where the original had one shared object.
Shallow Copy vs Deep Copy
The copy module also provides copy.copy, which creates a new container but leaves the contents shared. The distinction matters whenever a structure contains nested mutable objects.
import copy original = {"items": [1, 2, 3]} shallow = copy.copy(original) deep = copy.deepcopy(original) shallow["items"].append(4) print(original["items"]) # [1, 2, 3, 4] deep["items"].append(5) print(original["items"]) # [1, 2, 3, 4]
A shallow copy is sufficient when the top-level container is the only mutable part you need to isolate. A deep copy is required when nested objects can be modified independently.
Customizing Copy Behavior with deepcopy
Classes can control how deepcopy treats their instances by defining __deepcopy__. The method receives the memo dictionary and should return a new instance.
import copy class Connection: def __init__(self, url, cache): self.url = url self.cache = cache def __deepcopy__(self, memo): new = Connection(self.url, copy.deepcopy(self.cache, memo)) return new
This is useful when an object holds resources that should not be duplicated, such as an open socket or a file handle. The method lets you copy the state that matters while leaving the resource reference intact.
Circular References and Recursion Limits
deepcopy handles circular references through the memo dictionary. A list that contains itself is copied correctly:
import copy cycle = [] cycle.append(cycle) result = copy.deepcopy(cycle) print(result[0] is result) # True
The memo records the list before its contents are copied, so the recursive copy of the self-reference finds the already-created copy. Without memoization, this would recurse until Python raised a RecursionError.
Deeply nested structures can still hit the recursion limit because deepcopy is implemented recursively. A graph nested several thousand levels deep may raise RecursionError even when the structure is otherwise valid. Raising sys.setrecursionlimit can help, but it also increases the risk of a C-level stack overflow.
Performance and Memory Considerations
deepcopy is significantly more expensive than a shallow copy because it visits every reachable object and allocates a new one. The cost grows with the total size of the object graph, not just the number of top-level containers.
For large data structures copied frequently, the cost can dominate runtime. Consider whether the copy is actually necessary. Alternatives include:
- Using immutable structures such as tuples or
frozensetwhere copying is cheap or unnecessary. - Rebuilding only the modified portion of a structure.
- Using
copy.copywhen nested objects are never mutated. - Storing data in a format that supports cheap structural sharing, such as a persistent data structure.
If profiling shows that deepcopy is a bottleneck, the __deepcopy__ method on the relevant classes can reduce the work by copying only the fields that actually change.
Common Failure Modes
Some objects cannot be deep-copied. Modules, file objects, sockets, and certain C extension types raise TypeError when deepcopy encounters them. The error message varies by type, but the cause is the same: the object has no registered copy protocol.
import copy import threading lock = threading.Lock() try: copy.deepcopy(lock) except TypeError as exc: print(exc) # cannot pickle '_thread.lock' object
The fix is to implement __deepcopy__ on the containing class and return the original resource object while copying the rest of the state.
When deepcopy Is the Wrong Tool
deepcopy is not always the right answer. If you only need to protect a list from accidental mutation, a shallow copy or a tuple may be simpler. If you need to serialize data for storage or transmission, pickle or json may be more appropriate. If you need to clone a complex object graph frequently, consider whether the graph can be restructured to avoid deep copying altogether.
The decision depends on how the copied object will be used. A deep copy guarantees full isolation, but that guarantee costs time and memory. Understanding what deepcopy does internally makes it easier to decide when the guarantee is worth the cost.