Difference between Shallow and Deep Copy of a Class


A class is a blueprint or template that defines objects' attributes (data) and behaviours (methods). It's by far a fundamental concept of object-orientated programming (OOP) that allows you to create items based totally on the class definition.

Shallow copy

Shallow copy creates a new object that stores the reference of the original elements. Instead of making a duplicate of the nested objects, it just replicates their references. This means that a copy process does not recurse or create copies of nested objects itself. Shallow copy is faster than deep copy, but it is "lazy" and handles pointers and references.

It just replicates the pointer price rather than making a modern duplicate of the specific information the pointer links to. So, both the original and the copy will have pointers that reference constant underlying knowledge.

Shallow copy is often performed using the built-in copy.copy() method or the copy constructor.

Deep Copy

A deep copy creates a new object that stores copies of the original elements. It is not shared between the original and the copy. Deep copy actually clones the underlying data. It is not shared between the original and the copy. Deep copy is slower than shallow copy, but it creates a complete copy of the object and all of its children.

A deep copy can be achieved using the copy.deepcopy() method provided by the copy module.

Example

In this example, we will create an instance of a class and make its shallow and deep copy to understand how the copying process works and how it affects the memory addresses of the internal objects.

import copy

class MyClass:
   def __init__(self, name, numbers):
      self.name = name
      self.numbers = numbers

   def __repr__(self):
      return f"MyClass(name='{self.name}', numbers={self.numbers})"

# Creating an instance of MyClass
original_obj = MyClass("Rahul Ravindranath", [53, 27, 82])


print("Memory address of original_obj.name:", id(original_obj.name))
print("Memory address of original_obj.numbers:", id(original_obj.numbers))

# Making a shallow copy
shallow_copy = copy.copy(original_obj)

# Checking memory addresses of internal objects for shallow copy

print("Memory address of shallow_copy.name:", id(shallow_copy.name))
print("Memory address of shallow_copy.numbers:", id(shallow_copy.numbers))

# Creating a Deep copy
deep_copy = copy.deepcopy(original_obj)

# Checking memory addresses of internal objects for deep copy

print("Memory address of deep_copy.name:", id(deep_copy.name))
print("Memory address of deep_copy.numbers:", id(deep_copy.numbers))

Output

Memory address of original_obj.name: 23317216835824
Memory address of original_obj.numbers: 23317215777984
Memory address of shallow_copy.name: 23317216835824
Memory address of shallow_copy.numbers: 23317215777984
Memory address of deep_copy.name: 23317216835824
Memory address of deep_copy.numbers: 23317215706944

It is to be noted that the memory address varies for different devices.

From the above outputs, we infer that the memory addresses of shallow_copy.name and shallow_copy.numbers are the same as the memory addresses of the corresponding attributes in original_obj. This indicates that the internal objects are not duplicated but are referenced.

When we check the memory addresses of deep_copy.name and deep_copy.numbers, we find that they are different from the memory addresses of the corresponding attributes in original_obj. This shows that the internal objects have been duplicated, creating separate instances for deep_copy.

Example

In this code, we will create a class and make its shallow and deep copy then modify the copies with some operations and demonstrate the changes in the original and its copies.

Algorithm

  • Define a class named ClassEx with an __init__ method that initializes an instance with a name attribute and an empty list attribute.

  • Implement an add_item and __str__ method to append items to the list and provide a string representation of the instance.

  • Create an instance of ClassEx named original

  • Using add_item method append the required data to the original and its copies

  • Display the “original” instance and the shallow and deep copy

  • With the use of a list attribute and indexing replace some items in each object which includes original, shallow copy and deep copy.

  • Print the updated original object, shallow_copy, and deep_copy.

import copy

class ClassEx:
   def __init__(self, name):
      self.name = name
      self.list = []

   def add_item(self, item):
      self.list.append(item)

   def __str__(self):
      return f"{self.name}: {self.list}"

# Create an instance of ClassEx
original = ClassEx("Original")

# Add some items to the list
original.add_item("Item 1")
original.add_item("Item 2")

# Create a shallow copy of the object
shallow_copy = copy.copy(original)

# Add an item to the list of the shallow copy
shallow_copy.add_item("Item 3")

# Print the original object and the shallow copy
print("Original object:", original)
print("Shallow copy:", shallow_copy)

# Create a deep copy of the object
deep_copy = copy.deepcopy(original)

# Add an item to the list of the deep copy
deep_copy.add_item("Item 4")

# Print the original object and the deep copy
print("Original object:", original)
print("Deep copy:", deep_copy)

# Replace an item in the original object
original.list[0] = "New Item 1"

# Replace an item in the shallow copy
shallow_copy.list[1] = "New Item 2"

# Replace an item in the deep copy
deep_copy.list[2] = "New Item 3"

# Print the updated objects
print("Original object:", original)
print("Shallow copy:", shallow_copy)
print("Deep copy:", deep_copy)

Output

Original object: Original: ['Item 1', 'Item 2', 'Item 3']
Shallow copy: Original: ['Item 1', 'Item 2', 'Item 3']
Original object: Original: ['Item 1', 'Item 2', 'Item 3']
Deep copy: Original: ['Item 1', 'Item 2', 'Item 3', 'Item 4']
Original object: Original: ['New Item 1', 'New Item 2', 'Item 3']
Shallow copy: Original: ['New Item 1', 'New Item 2', 'Item 3']
Deep copy: Original: ['Item 1', 'Item 2', 'New Item 3', 'Item 4']

The shallow copy shares the internal objects with the original, while the deep copy creates independent copies of the internal objects. Changes made in shallow copy and original object affect each other while the deep copy is completely separate and doesn't affect the original object when modifications are done in deep copy.

Key Differences

Shallow Copy Deep Copy
Memory Sharing Shares memory locations with the original object Creates an independent copy with separate memory locations
Performance Faster and more efficient, especially for large objects or complex data structures Slower and may consume more memory, particularly for large objects or deep nesting
Data Integrity May lead to unintended modifications of the original object Ensures data integrity by isolating the copied object
Changes to Copied Object Modifications can affect the original object and vice versa Modifications do not affect the original object
Dependencies and References Shared references can cause unintended side effects or data integrity issues Handles circular references and complex interdependencies by copying all nested objects

Conclusion

A shallow copy is used to Create a backup or snapshot of an object's current state. It is preferred when Implementing copy-on-write behaviour, where a copy is only made when changes are made to the copied object and for creating a new object with initial values based on an existing object.

Deep copy is used when we want to Create a completely independent copy of an object and its nested objects. It is helpful in the Serialization and deserialization of objects, where the copied object needs to be stored or transmitted independently.

Updated on: 10-Aug-2023

192 Views

Kickstart Your Career

Get certified by completing the course

Get Started
Advertisements