Deep vs Shallow Copies in Python

Introduction

In this tutorial, we are going to discuss shallow copies vs deep copies with the help of examples in Python. We will cover the definition of a deep and shallow copy, along with its implementation in the Python language to evaluate the core differences between the two types of copies.

In many of the programs that we write, no matter how basic they are, we end up needing to copy a list or an object for one of many reasons, like computational efficiency. There are two ways to do that, either make a deep copy or a shallow copy. Before we discuss the differences between the two, let's first understand what deep and shallow copies exactly are.

Deep Copies in Python

A deep copy makes a new and separate copy of an entire object or list with its own unique memory address. What this means is that any changes you make in the new copy of the object/list won't reflect in the original one. This process happens by first creating a new list or object, followed by recursively copying the elements from the original one to the new one.

To put it briefly, both of the objects are completely independent of each other. This is similar to the concept of passing by value in languages like C++, Java, and C#.

Deep Copy Example

To implement the concept of deep copies in Python, we'll be using the copy module.

Let's say we have a list of lists called result_A, which contains grades of student A in 3 subjects for the first two years of school, and we wish to create an exactly similar list of results for student B as well. We'll try and make a deep copy of the result_A list, and make a few changes in that deep copy later to show the grades of student B.

Example 1:

# Program 1 - Deep Copy
import copy

result_A = [[90, 85, 82], [72, 88, 90]] # Student A grades  
result_B = copy.deepcopy(result_A) # Student B grades (copied from A)

print(result_A)  
print(result_B)  

In the script above, we use the deepcopy method from the copy module to copy list result_A to result_B. Next, we print the contents of both the lists on the screen.

Output:

[[90, 85, 82], [72, 88, 90]]
[[90, 85, 82], [72, 88, 90]]

As you can see, the lists are identical. Later on in this article we'll see how this is different from shallow copies.

Shallow Copies in Python

A shallow copy also makes a separate new object object or list, but instead of copying the child elements to the new object, it simply copies the references to their memory addresses. Hence, if you make a change in the original object, it would reflect in the copied object, and vice versa. To put it briefly, both the copies are dependent on each other. This is similar to the concept of passing by reference in programming languages like C++, C#, and Java.

Shallow Copy Example

To implement this in Python, we'll use the copy module again, but this time we'll be calling its copy function.

Let's use the same example list for the shallow copy example as well.

Example 2:

# Program 2 - Shallow Copy
import copy

result_A = [[90, 85, 82], [72, 88, 90]]  
result_B = copy.copy(result_A)

print(result_A)  
print(result_B)  

In the script above, we use the copy method from the copy module to make a shallow copy of list result_A which we named result_B. Next, the contents of both the lists have been printed on the console.

Output:

[[90, 85, 82], [72, 88, 90]]
[[90, 85, 82], [72, 88, 90]]

Again, the lists are the same, as expected. Next we'll explain the difference between the results we get from the copy and deepcopy functions.

Difference Between Deep and Shallow Copies

Now that we have discussed what shallow and deep copies are and why we create copies, it's time to talk about the difference between them. Essentially, there are just two core differences and they're linked with each other:

  1. Deep copy stores copies of an object's values, whereas shallow copy stories references to the original memory address
  2. Deep copy doesn't reflect changes made to the new/copied object in the original object; whereas, shallow copy does

Before we move on to the implementation, I'd like you to imagine this scenario. Let's say two people wish to share a drink; they have two empty glasses and two straws. They can share this drink in two ways:

  1. Put the drink in one glass, and put both the straws in that glass for sharing
  2. Put the drink in both the glasses, and put one straw in each glass

The first scenario is that of a shallow copy. Both the variables/instances are pointing/using the same memory location for their operations. The second scenario is that of a deep copy. Both variables/instances are pointing to/using two different memory locations for their operations.

Comparison Example

To make the difference clear, let's use this information in our above two examples, starting with Example 1.

Above, we created the list result_A and made a deep copy of it named result_B. Let's try to change the content in result_B and see if it has any effect on the contents of result_A.

import copy

result_A = [[90, 85, 82], [72, 88, 90]] # Student A grades  
result_B = copy.deepcopy(result_A) # Student B grades (copied from A)

# Change first year and first subject's marks to 30
result_B[0][0] = 30

print("Original List: ")  
print(result_A)  
print("Deep Copy:")  
print(result_B)  

Output:

Original List:  
[[90, 85, 82], [72, 88, 90]]
Deep Copy:  
[[30, 85, 82], [72, 88, 90]]

The expected outcome is that the original list remains unchanged. And as you can see, changes in the deep copy didn't affect the original list.

Now, lets try the same thing with Example 2 - Shallow Copy.

import copy

result_A = [[90, 85, 82], [72, 88, 90]] # Student A grades  
result_B = copy.copy(result_A) # Student B grades (copied from A)

# Change first year and first subject's marks to 30
result_B[0][0] = 30

print("Original List: ")  
print(result_A)  
print("Shallow Copy:")  
print(result_B)  

Output:

Original List:  
[[30, 85, 82], [72, 88, 90]]
Shallow Copy:  
[[30, 85, 82], [72, 88, 90]]

Here the expected outcome is that both the original list and copied list are modified after the single change. And as you can see, making the change to the shallow copy resulted in that change being reflected in the original list as well.

Conclusion

In this post, we talked about what a shallow and a deep copy is and how we can make them in Python language using the 'copy' module. We used two of its functions i.e. copy and deepcopy to make shallow and deep copies, respectively. Furthermore, we discussed the two core differences between a shallow and a deep copy, and also implemented shallow and deep copy in python to understand those differences in a better way.