Python Object Copy: Shallow vs Deep Explained
python object copy: Learn how to copy objects in Python correctly. Understand the difference between shallow and deep copy, when to use each, and how to customize copy...
Many Python developers assume that assigning one variable to another creates a copy of the object. In reality, assignment only copies the reference. Understanding python object copy is essential to avoid subtle bugs when working with mutable data structures.
Assignment vs Copy: The Reference Problem
When you write b = a, Python does not create a new object. Both a and b point to the same object in memory. For immutable types like integers or strings, this is harmless because the value cannot change. But for mutable objects such as lists, dictionaries, and custom class instances, modifying one variable affects the other.
a = [1, 2, 3] b = a b.append(4) print(a) # [1, 2, 3, 4]
This behavior is often unexpected for developers coming from languages where assignment copies values. To actually duplicate an object, you need an explicit copy operation. Python provides the copy module for this purpose.
Shallow Copy with copy.copy()
The copy.copy() function creates a shallow copy of an object. A shallow copy constructs a new container object and then populates it with references to the elements found in the original. This means the top-level object is new, but nested objects are shared.
import copy a = [[1, 2], [3, 4]] b = copy.copy(a) b[0].append(99) print(a) # [[1, 2, 99], [3, 4]] print(b) # [[1, 2, 99], [3, 4]]
Here, b is a new list, but its inner lists are the same objects as in a. Modifying a nested list through b also changes a. This is the core limitation of a shallow copy.
Shallow copies are appropriate when you only need to protect the top-level structure, or when the object contains only immutable elements.
Deep Copy with copy.deepcopy()
A deep copy creates a new object and recursively copies all objects found within it. The result is fully independent of the original, including nested containers and custom objects.
import copy a = [[1, 2], [3, 4]] b = copy.deepcopy(a) b[0].append(99) print(a) # [[1, 2], [3, 4]] print(b) # [[1, 2, 99], [3, 4]]
Deep copies eliminate shared references entirely. They are the safest choice when you need to modify a copy without any side effects on the original. However, deep copying is more expensive because it traverses the entire object graph.
Customizing Copy Behavior with copy and deepcopy
For user-defined classes, you can control how copies are made by defining the __copy__ and __deepcopy__ methods. These methods are called by copy.copy() and copy.deepcopy() respectively.
import copy class Config: def __init__(self, values): self.values = values def __copy__(self): # Return a shallow copy with a new list for values return Config(list(self.values)) def __deepcopy__(self, memo): # Return a deep copy, using copy.deepcopy on attributes return Config(copy.deepcopy(self.values, memo))
The memo parameter in __deepcopy__ is a dictionary that tracks already-copied objects to prevent infinite recursion in cyclic structures. Always pass it through when copying nested attributes.
Customizing copy behavior is useful when your class contains resources that should not be duplicated, such as file handles or network connections. You can choose to share them or recreate them in the copy.
Copying Nested Data Structures: Common Pitfalls
One frequent mistake is using a shallow copy when a deep copy is required, especially with dictionaries that contain lists or other dictionaries.
import copy original = {"items": [1, 2, 3]} shallow = copy.copy(original) shallow["items"].append(4) print(original["items"]) # [1, 2, 3, 4]
Another pitfall is copying objects that contain references to themselves. copy.deepcopy() handles cycles correctly, but a naive recursive copy would cause infinite recursion. The memo dictionary in __deepcopy__ is exactly for this purpose.
Also be aware that copy.copy() and copy.deepcopy() work on most built-in types. For custom classes, if you do not define __copy__ or __deepcopy__, the default implementation copies __dict__ and attempts to recreate the object. This can fail for objects that require constructor arguments. Defining these methods explicitly gives you full control.
Performance and Memory Considerations
Deep copying is significantly slower and uses more memory than shallow copying because it duplicates every nested object. For large data structures, this can become a bottleneck. Shallow copies are inexpensive but leave shared references.
The choice between shallow and deep copy should be driven by how the copy will be used. If you only need to replace elements at the top level, a shallow copy suffices. If you need to modify nested data without affecting the original, a deep copy is required.
For performance-sensitive code, consider whether you can avoid copying altogether by using immutable data structures or by restructuring the logic to not require copies. When copying is unavoidable, profile the operation to understand its cost.
Choosing Between Shallow and Deep Copy
Use a shallow copy when:
- The object contains only immutable elements.
- You intend to replace top-level items without modifying nested ones.
- You explicitly want to share nested objects for performance reasons.
Use a deep copy when:
- The object contains mutable nested structures that will be modified.
- You need a fully independent snapshot of the object.
- You are working with complex objects that may contain cycles.
A practical approach is to start with a shallow copy and only switch to deep copy when you encounter shared-reference bugs. This keeps the code efficient while avoiding unnecessary duplication.
Remember that copy.copy() and copy.deepcopy() are not limited to built-in containers. They work with any object that supports the copy protocol. For custom classes, implementing __copy__ and __deepcopy__ gives you precise control over what gets duplicated and what gets shared.