Python offers several efficient approaches to managing data. Understanding shallow and deep copy concepts is crucial when working with data structures like nested lists, dictionaries, or custom objects.
Both shallow and deep copy let you make replicas of data structures, but they act differently regarding nested data.
Using Shallow Copy
Shallow copy works by creating a copy of the top-level structure of the original object. This means that, if the original object contains nested objects, the copy will reference the same nested objects that the original does. In other words, making a shallow copy of an object duplicates its outermost structure, not any nested objects it may contain.
To perform a shallow copy in Python, you can use the copy module's copy() function or the .copy() method on the object.
Consider an example of working with a list or dictionary in Python.
import copy
main_list = [29, 49, ["Q", "R"]]
shallow_copy = copy.copy(main_list)
# Modify the nested list
shallow_copy[2][0] = 99
main_list[2][1] = 100
print(f"The main list: {main_list}")
print(f"The shallow copy list: {shallow_copy}")
In the code above, the main_list variable holds a list containing integers and an inner list (nested object) containing letters. The copy function creates a copy of the main_list which the code stores in another variable, shallow_copy.
Any changes you make to the shallow_copy nested list will also directly affect that of the main_list and vice versa. These changes show that the nested or inner list of the shallow_copy is just a reference to that of the main_list, making the changes apply in main_list too.
Meanwhile, any changes made to the outer items (the integers) in either shallow_copy or main_list will only affect that instance. These outer items are independent values in their own right, not just mere references.
import copy
main_list = [29, 49, ["Q", "R"]]
shallow_copy = copy.copy(main_list)
# Modify the outer items
shallow_copy[0] = "M"
main_list[1] = "N"
print(f"The main list: {main_list}")
print(f"The shallow copy list: {shallow_copy}")
The output demonstrates that both list’s outer items are independent of each other:
The same idea applies when working with dictionaries.
dict1 = {'ten': 10, 'twenty': 20, 'double':{'thirty': 30, 'sixty': 60}}
dict2 = dict1.copy()
# Modify inner and outer elements
dict1['double']['thirty'] = 30.00
dict1['ten'] = 10.00
print(f"The main dictionary, {dict1}")
print(f"The shallow copy dictionary, {dict2}")
Changes made to the nested dictionary of dict1 affect both dict1 and dict2. At the same time, changes to the outer items of dict1 affect only it.
Using Deep Copy
Instead of referencing the nested objects of the original copy, a deep copy makes an entirely separate copy of the original object and its nested objects. Modifying the deep copy will not affect the original object and vice versa; they are truly separate values.
To make a deep copy in Python, use the deepcopy() function of the copy module.
Consider an example of working with a list.
import copy
main_list = [200, 300, ["I", "J"]]
deep_copy = copy.deepcopy(main_list)
# Modify the inner and outer list
deep_copy[2][0] = "K"
main_list[0] = 500
print(f"The main list: {main_list}")
print(f"The deep copy list: {deep_copy}")
Here, the code performs a deep copy of main_list, creating an independent copy named deep_copy.
When you modify the nested list or outer items in the deep_copy, your changes do not affect the original list, and vice versa. This demonstrates that the nested list or outer elements are not shared between the two copies.
Working With Custom Objects
You can create a custom object by defining a Python class and creating an instance of the class.
Here's an example of creating a simple object from a Book class:
class Book:
def __init__(self, title, authors, price):
self.title = title
self.authors = authors
self.price = price
def __str__(self):
return f"Book(title='{self.title}', author='{self.authors}', \
price='{self.price}')"
Now, make both a shallow copy and a deep copy of an instance of this Book class using the copy module.
import copy
# Create a Book object
book1 = Book("How to MakeUseOf Shallow Copy", \
["Bobby Jack", "Princewill Inyang"], 1000)
# Make a shallow copy
book2 = copy.copy(book1)
# Modify the original object
book1.authors.append("Yuvraj Chandra")
book1.price = 50
# Check the objects
print(book1)
print(book2)
As you can see, the shallow copy (book2) is a new object, but it references the same inner object (author list) as the original object (book1). Hence, a change to the original object’s authors affects both instances (book1 and book2), while a change to the outer item (price) only affects the original object (book1).
On the other hand, making a deep copy creates an independent copy of the original object, including copies of all objects contained within it.
# Create a Book object
book1 = Book("Why MakeUseOf Deep Copy?", \
["Bobby Jack", "Yuvraj Chandra"], 5000)
# Make a deep copy
book2 = copy.deepcopy(book1)
# Modify the original object
book1.authors.append("Princewill Inyang")
book1.price = 60
# Check the objects
print(book1)
print(book2)
In this case, the deep copy (book2) is a completely independent object, and modifying the original object (book1) does not affect it.
Uses for Shallow Copy and Deep Copy
It’s vital to understand deep and shallow copy so you can select the appropriate approach for manipulating data. Here are some scenarios where each method is applicable:
- Use a shallow copy if you want to replicate a complex object without generating new instances of its nested objects. This approach is more memory efficient and faster than deep copy because it doesn't duplicate nested objects.
- Use a shallow copy to create a snapshot of an object's state while still sharing some underlying data between the original and copied objects.
- Use a deep copy if you want to modify a replica of an object without impacting the original. This generates independent copies of nested objects, ensuring that any changes to the copy do not apply to the original.
- Deep copy is critical when you need independent copies of nested data structures, mainly when dealing with recursive or intricate object hierarchies.
Performance and Considerations
Since shallow copy doesn't generate new instances of nested objects, it typically runs faster and uses less memory than deep copy. However, the original and the shallow copy may have unwanted side effects from changing shared internal items.
Particularly for big and deeply nested data structures, deep copy, a recursive procedure, can be slower and use more memory. However, it ensures total independence between the original and the deep duplicate, making intricate data manipulation more secure.
The Best Copy Option for Your Data
Many programming languages use the concept of shallow and deep copy. Understanding it lets you manipulate data without unforeseen consequences.
By using shallow and deep copy techniques, you can select the best approach to duplicate your data structures safely. By understanding the effects on your data, you’ll get more dependable and predictable outcomes from your code.