Shallow copy and deep copy are two ways of copying objects in Python.
A shallow copy only copies the references to the elements making up the original object. When we perform an operations from either the copy or the original object, if the operation changes a shared element, the change is reflected on the other object.
On the other hand, a deep copy makes a duplicate of the elements making up the original object, which takes up a different memory locations , so any changes on either object are not reflected on the other.
Assignment vs Copy
You should not confuse between copy and assignment operations. Copying means creating a new object with the exact same content as an existing object, while assignment involves linking a name to the object, so that it can be accessed by that name. When copying, you create two separate objects with same values, while with assignment, you get a single object referenced by two(or more) names.
Assignment example
#both
a = [1, 2, 3, 4]
b = a
b.append(5)
a.append(6)
b.append(7)
a.append(8)
print(a)
print(b)
In the above example a
and b
literally refers to the same object and thus there is no copy operation going on.
The copy
module in the standard library provide tools necessary for shallow copying as well as deep copying.
shallow copies
Shallow copies stores references to the elements making up the original object, rather than replicating them. You should note that you will have two separate objects only that they share commons elements.
The copy()
function in the copy
module is used to perform shallow copies.
this demonstrates how shallow copy works
import copy
class Person:
def __init__(self, name):
self.name = name
def __repr__(self):
return f"Person({self.name})"
p1 = Person('John')
p2 = Person('Smith')
L = [p1, p2]
L2 = copy.copy(L)
L[0].name = "Mary"
#the change is seen in L2
print(L2)
#this does not affect L2
L.append(Person('Mike'))
print(L2)
As shown in the above example, when we perform a shallow copy, we get two separate objects but that are made of same elements. Thus, if we change an element that makes up both, the effect will be reflected on both objects. This means that even if we change the objects through the copy, the effect will be seen on the original object.
import copy
class Person:
def __init__(self, name):
self.name = name
def __repr__(self):
return f"Person({self.name})"
p1 = Person('John')
p2 = Person('Smith')
L = [p1, p2]
L2 = copy.copy(L)
L2[0].name = "Mary"
#the change is seen in the original object
print(L)
L2[1].name = 'Morris'
print(L)
Deep copies
When we perform a deep copy operation, a new objects is created with duplicate elements of the original object. This duplicate elements are allocated separate memory locations, meaning that any operation on either the original or the copy will not be reflected on the other.
We use the deepcopy()
function to create deep copies.
import copy
class Person:
def __init__(self, name):
self.name = name
def __repr__(self):
return f"Person({self.name})"
p1 = Person('John')
p2 = Person('Smith')
L = [p1, p2]
L2 = copy.deepcopy(L)
#change to original does not affect the copy
L[0].name = "Mary"
print(L2)
#Change to copy does not affect the original object
L2[1].name = 'Morris'
print(L)
Customizing copy behaviour in custom objects
The __copy__()
and the __deepcopy__ ()
dunder methods can be used to customize how the interpreter handles copying the contents of objects.
The __copy__()
method is called without any argument and should return the shallow copy of the object.
import copy
class Person:
def __init__(self, name, buddy = None):
self.name = name
if buddy:
self.create_buddy(buddy)
def create_buddy(self, other):
self.buddy = other
other.buddy = self
def get_buddy(self):
return self.buddy.name
def __copy__(self):
#creating a copy
print('\n', "created a shallow copy of %s"%self.name)
return Person(self.name, self.buddy)
p = Person('John')
p1 = Person('Mike', p)
print(f"p: {p.name}'s buddy is {p.get_buddy()}")
print(f"p1: {p1.name}'s buddy is {p1.get_buddy()}")
#create a copy
p2 = copy.copy(p1)
print(f"p2: {p2.name}'s buddy is {p2.get_buddy()}")
print(p1 == p2)
print(p1.buddy == p2.buddy)
The __deepcopy__()
dunder method should return the deep copy of the object. We can override this method to customize how deep copy operation is carried on the objects of the class.