Copy Objects in Python

Learn how to copy Python objects using shallow and deep copy, how the copy module works, and how to implement __copy__ and __deepcopy__ for custom classes.

6 min read

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:

pythonpython
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:

pythonpython
import copy
 
class DataSet:
    def __init__(self, data):
        self.data = list(data)
        self._cache = {}
 
    def __copy__(self):
        new = DataSet(self.data)
        return new

The 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

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.
RunePowered by Rune AI

Frequently Asked Questions

What is the difference between shallow copy and deep copy in Python?

A shallow copy creates a new object but inserts references to the same nested objects that the original contains. If you modify a nested object through the copy, the original sees the change because both share the same nested object. A deep copy creates a new object and recursively copies all nested objects, so the copy is completely independent of the original. Shallow copy is faster and uses less memory; deep copy is safer when you need complete isolation.

How do I copy a Python object?

For built-in types, use the copy method on lists, dictionaries, and sets, or use slicing for lists with my_list[:]. For custom objects, use the copy module: copy.copy() for shallow copies and copy.deepcopy() for deep copies. The copy module works with any Python object by default, and you can customize the copy behavior for your classes by implementing __copy__ and __deepcopy__ magic methods.

When should I implement __copy__ or __deepcopy__ on my class?

Implement custom copy methods when the default copy behavior does not produce the correct result for your class. This commonly happens when your class manages external resources like file handles or network connections that should not be duplicated, or when the default copy logic is inefficient because it copies internal caches or temporary data that can be regenerated. Most classes do not need custom copy methods; the defaults work correctly for objects composed of standard Python types.

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.