Python object copying is the process of creating a new object that duplicates the state of an existing one, and the distinction between shallow and deep copying is essential knowledge for any developer working with mutable objects. When you assign an object to a new variable, you are not creating a copy; you are creating a second reference to the same object. Modifying the object through either reference affects the same underlying data. Creating an actual copy requires either the copy module from the standard library or a class-specific copying method. The copy module provides two functions: copy for shallow copies and deepcopy for deep copies. The difference between them is what happens to the objects nested inside the one you are copying.
Understanding object copying matters because it is the source of one of the most common and hardest-to-diagnose categories of bugs in Python programs. You modify a list that you thought was independent, and another part of your program breaks because it was sharing that same list through a shallow copy. You pass a dictionary to a function expecting the function to work on a copy, and the function mutates the original because no copy was made. These bugs are subtle because the program runs without errors; the data is simply wrong in ways that may not be obvious until much later. The article on Python instance attributes covered how attributes store data on objects, and copying is the operation that determines whether two objects share that data or have independent copies of it.
The copy module has been part of Python's standard library since the early days of the language, and it works with any Python object through a combination of default behavior and customizable magic methods. For most classes composed of standard Python types, the default copy behavior is correct and requires no customization. For classes that manage external resources, maintain internal caches, or have attributes that should not be duplicated, implementing custom copy methods gives you control over exactly what a copy of your object looks like.
Assignment versus shallow copy versus deep copy
The three levels of object duplication form a spectrum from no duplication to complete duplication, and choosing the right level for each situation prevents both unnecessary memory usage and unintended data sharing. Assignment creates no new object at all; it creates a new name that refers to the same object. Shallow copy creates a new object but does not copy the objects nested inside it; the copy shares references to those nested objects with the original. Deep copy creates a new object and recursively copies every object reachable from it, producing a completely independent clone.
Here is a demonstration of the three levels using a list of lists:
import copy
original = [[1, 2], [3, 4]]
assigned = original
shallow = copy.copy(original)
deep = copy.deepcopy(original)Modifying the assigned variable changes the original because they are the same object. Modifying a nested list through the shallow copy changes the original because both share the same inner lists. Modifying anything through the deep copy leaves the original untouched because every level was independently duplicated. The choice of which copy level to use depends on whether the nested objects should be shared or independent.
For a class that holds a list of items, a shallow copy might be appropriate if the items themselves are immutable, like strings or integers. Modifying the list by adding or removing items affects only the copy, because the list object itself is distinct. A deep copy would additionally copy every string in the list, which is wasteful because strings are immutable and sharing them is safe. For a class that holds a list of mutable objects, like other custom class instances, a deep copy is often necessary to prevent the copy and the original from interfering with each other through the shared mutable objects.
Customizing copy behavior with magic methods
The copy module calls special methods on your objects when making copies, and you can override these methods to customize what a copy of your class looks like. The copy function looks for a method called copy on the object. If it exists, the function calls it and returns the result. If it does not exist, the function uses a default mechanism that creates a new instance of the same class and copies the object's internal attribute dictionary.
Customizing shallow copy is useful when the default mechanism would copy attributes that should be reset or regenerated in the copy. A class that maintains a cache of computed values might want the copy to start with an empty cache rather than sharing the original's cache. A class that holds an open file handle might want the copy to start with no file handle rather than sharing the same one. Here is a class that customizes its shallow copy behavior:
import copy
class DataSet:
def __init__(self, data):
self.data = list(data)
self._cache = {}
def __copy__(self):
new = DataSet(self.data)
return newThe custom copy method creates a new DataSet with the same data but a fresh empty cache. The default copy mechanism would have copied the cache dictionary as well, giving the copy access to stale cached values from the original. The custom method ensures that the copy starts with a clean state.
For deep copies, the deepcopy function looks for a method called deepcopy on the object. This method receives a memo dictionary that tracks objects already copied during the recursive deep copy process, preventing infinite recursion when objects contain circular references. Customizing deep copy is rarely necessary because the default algorithm handles most cases correctly, but it is available for classes with special requirements.
The article on class variables and instance variables covers how attributes at different levels interact, and that interaction matters when copying objects. Class attributes are shared by default and are not copied; only instance attributes are duplicated during a copy operation.
Rune AI
Key Insights
- A shallow copy duplicates the object itself but shares references to nested objects; a deep copy recursively duplicates everything.
- Use copy.copy() for shallow copies and copy.deepcopy() for deep copies, both from the standard library copy module.
- Shallow copies are appropriate when nested objects are immutable or shared access is intentional.
- Deep copies are necessary when the copy must be completely independent of the original.
- Implement copy and deepcopy on your classes only when the default copy behavior is incorrect or inefficient.
Frequently Asked Questions
What is the difference between shallow copy and deep copy in Python?
How do I copy a Python object?
When should I implement __copy__ or __deepcopy__ on my class?
Conclusion
Copying objects is a common operation that becomes subtle when objects contain references to other objects. Shallow copies are fast and sufficient when you only need to duplicate the top-level structure. Deep copies are thorough and safe when you need complete independence between the original and the copy. Understanding the difference and knowing how to customize copy behavior for your classes ensures that your objects behave correctly when duplicated.
More in this topic
Create Custom Iterators in Python
Learn how to build your own iterators in Python by implementing the __iter__() and __next__() methods, with practical examples and reusable patterns.
Python Iterator Protocol Explained
Learn how Python's iterator protocol works with the __iter__() and __next__() methods, and understand the contract every iterator must follow.
Python Iterables and Iterators Explained
Learn what iterables and iterators are in Python, how they power for loops under the hood, and why understanding them unlocks cleaner data processing patterns.