BlogsDope image BlogsDope

Shallow Copy and Deep Copy in Python

Nov. 18, 2020 PYTHON 415

We often want to create a real copy or a clone of an object to make changes in one without affecting the other. Assignment operators, however, do not help us achieve that. It just creates a new variable that refers to the same object. For immutable objects, it does not cause a problem, but for mutable objects, it does. Let’s see how.

list_1 = [1,2,3,4,5]
list_2 = list_1
list_2[0] = 2
print(f"list_1 contents: {list_1}")
print(f"list_2 contents: {list_2}")
print(f"Memory address of object list_1: {id(list_1)}")
print(f"Memory address of object list_2: {id(list_2)}")

Output

list_1 contents: [2, 2, 3, 4, 5]
list_2 contents: [2, 2, 3, 4, 5]
Memory address of object list_1: 140360051669960
Memory address of object list_2: 140360051669960

In the above example, we used the assignment operator. As you can see, changing the contents of the copied list also changed the original because they point to the same memory address.

Instead, we can create a shallow copy to clone the object. A shallow copy constructs a new compound object and then inserts the references to the child objects from the original one into it. It is only one level deep, which means that for compound objects (for example, nested lists), it does not create copies of child objects, i.e., it does not recurse.

shallow copy in python

To create a shallow copy, we will use the copy module.

import copy

list_1 = [1,2,3,4,5]
list_2 = copy.copy(list_1)
list_2[0] = 2
print(f"list_1 contents: {list_1}")
print(f"list_2 contents: {list_2}")
print(f"Memory address of object list_1: {id(list_1)}")
print(f"Memory address of object list_2: {id(list_2)}")

Output

list_1 contents: [1, 2, 3, 4, 5]
list_2 contents: [2, 2, 3, 4, 5]
Memory address of object list_1: 140360051766792
Memory address of object list_2: 140360052725576

As you can see, we got the desired behavior, i.e., changing list_2 did not affect list_1, using the copy() method of the copy module. You can also observe that both the variables point to different memory addresses. This is one method of creating a shallow copy. You can also use the list() method or the slice operator.

list_1 = [1,2,3,4,5]
list_2 = list(list_1) #list() method
list_2[0] = 2
print(f"list_1 contents: {list_1}")
print(f"list_2 contents: {list_2}")
print(f"Memory address of object list_1: {id(list_1)}")
print(f"Memory address of object list_2: {id(list_2)}")
list_1 = [1,2,3,4,5]
list_2 = list_1[:] #slice operator
list_2[0] = 2
print(f"list_1 contents: {list_1}")
print(f"list_2 contents: {list_2}")
print(f"Memory address of object list_1: {id(list_1)}")
print(f"Memory address of object list_2: {id(list_2)}")

How about compound objects? Would a shallow copy work? As already explained, it won’t. Let’s go through an example.

list_1 = [1,2,3, [4, 5], 6]
list_2 = copy.copy(list_1)
list_2[3][0] = 2
print(f"list_1 contents: {list_1}")
print(f"list_2 contents: {list_2}")
print(f"Memory address of object list_1: {id(list_1)}")
print(f"Memory address of object list_2: {id(list_2)}")
print(f"Memory address of child object list_1: {id(list_1[3])}")
print(f"Memory address of child object list_2: {id(list_2[3])}")

Output

list_1 contents: [1, 2, 3, [2, 5], 6]
list_2 contents: [1, 2, 3, [2, 5], 6]
Memory address of object list_1: 140360052656520
Memory address of object list_2: 140360052723976
Memory address of child object list_1: 140360052760392
Memory address of child object list_2: 140360052760392

In the above example, the original list contains another list at index 3. As you can see in the output, both variables list_1 and list_2 refer to the same inner list. Therefore, changes at the second level got reflected in both lists.

The copy module provides us the deepcopy() method to make a copy at all levels. The deep copy constructs a new compound object and then recursively inserts copies of the child objects from the original one into it. Let’s see.

deep copy in python

list_1 = [1,2,3, [4, 5], 6]
list_2 = copy.deepcopy(list_1)
list_2[3][0] = 2
print(f"list_1 contents: {list_1}")
print(f"list_2 contents: {list_2}")
print(f"Memory address of object list_1: {id(list_1)}")
print(f"Memory address of object list_2: {id(list_2)}")
print(f"Memory address of child object list_1: {id(list_1[3])}")
print(f"Memory address of child object list_2: {id(list_2[3])}")

Output

list_1 contents: [1, 2, 3, [4, 5], 6]
list_2 contents: [1, 2, 3, [2, 5], 6]
Memory address of object list_1: 140360051510216
Memory address of object list_2: 140360051184968
Memory address of child object list_1: 140360052656520
Memory address of child object list_2: 140360052760392

We got the desired output. Changes in one list at any level did not affect the other.

Let’s now go a step further and make a shallow copy and deep copy of the custom object.

class Student:
  def __init__(self, name, grade):
    self.name = name
    self.grade = grade

print("Shallow Copy")
s1 = Student("Ashton", 12)
# creating a shallow copy
s2 = copy.copy(s1)

s1.grade = 11
print(f"s1: {s1.grade}")
print(f"s2: {s2.grade}")

print("Deep Copy")
s1 = Student("Ashton", 12)
# creating a deep copy
s2 = copy.deepcopy(s1)

s1.grade = 11
print(f"s1: {s1.grade}")
print(f"s2: {s2.grade}")

In the above example, the class Student contains the properties name and grade. We created an instance of Student s1 and made its deep and shallow copies. In both cases, we got the same desired output because it is only one level deep. Let’s now create a clone of a custom compound object.

class Student:
  def __init__(self, name, grade):
    self.name = name
    self.grade = grade

class School:
  def __init__(self, student_1, student_2):
    self.student_1 = student_1
    self.student_2 = student_2

print("Shallow Copy")
s1 = Student("Ashton", 12)
s2 = Student("Aby", 11)

orig_school = School(s1, s2)
copy_school = copy.copy(orig_school)
orig_school.student_1.grade = 11
print(f"Original School Object: {orig_school.student_1.grade}")
print(f"Copy School Object: {copy_school.student_1.grade}")

print("Deep Copy")
s1 = Student("Ashton", 12)
s2 = Student("Aby", 11)

orig_school = School(s1, s2)
copy_school = copy.deepcopy(orig_school)
orig_school.student_1.grade = 11
print(f"Original School Object: {orig_school.student_1.grade}")
print(f"Copy School Object: {copy_school.student_1.grade}")

Output

Shallow Copy
Original School Object: 11
Copy School Object: 11
Deep Copy
Original School Object: 11
Copy School Object: 12

In addition to the Student class, we also have the School class now that has two Student objects as its properties. When we created an instance of the School class, we got a compound object. The shallow copy copies the references of the Student objects to the copy_school object. Hence, both the variables refer to the same objects s1 and s2. However, the deep copy copies the entire object.

The process of creating a deep copy is slower because child objects get recursively copied. However, unlike a shallow copy, a deep copy is completely independent of the original object.


Liked the post?
A computer science student having interest in web development. Well versed in Object Oriented Concepts, and its implementation in various projects. Strong grasp of various data structures and algorithms. Excellent problem solving skills.
Editor's Picks
0 COMMENT

Please login to view or add comment(s).