Click to share! ⬇️

In Python, objects are stored in memory and each object has a unique memory address. When you assign an object to a new variable, you are actually creating a reference to the original object’s memory location rather than creating a new object. This means that if you modify the object through the new variable, it will affect the original object as well. Python copy refers to the process of creating an independent duplicate of an existing object, instead of just creating a reference to the original object. Copying objects in Python can be done in two ways: shallow copy and deep copy. These methods are important to understand, as they determine the behavior of the copied objects and their relationship to the original objects.

A shallow copy creates a new object with a new memory address, but it only copies the top-level elements of the original object. If the original object contains other objects, such as lists or dictionaries, the references to those objects are copied, but the objects themselves are not. This means that changes made to the nested objects within the copied object will still affect the original object.

On the other hand, a deep copy creates a completely independent copy of the original object and all its nested objects. This means that any changes made to the copied object or its nested objects will not affect the original object.

Understanding the difference between shallow and deep copying is crucial when working with mutable objects like lists, dictionaries, or custom classes, as it helps you control the behavior of your code and avoid unexpected side effects.

What is Shallow Copy

A shallow copy is a copying method in Python that creates a new object with a distinct memory address, but only duplicates the top-level elements of the original object. If the original object contains nested objects, such as lists, dictionaries, or custom classes, the references to those nested objects are copied, but the nested objects themselves are not duplicated. This means the new object and the original object will still share the same nested objects.

In a shallow copy, changes made to the top-level elements in the copied object do not affect the original object. However, if you modify any nested objects within the copied object, these changes will also be reflected in the original object since they share the same nested objects.

For example, consider the following list of lists:

original_list = [[1, 2, 3], [4, 5, 6]]

If you create a shallow copy of this list:

import copy
shallow_copy = copy.copy(original_list)

The shallow_copy will have a new memory address, and any changes made to the top-level elements won’t affect the original_list. However, if you modify any nested elements within the shallow_copy, those changes will be reflected in the original_list because they share the same nested objects.

Shallow copies are generally faster and use less memory compared to deep copies, but it is important to understand their limitations when working with complex data structures or mutable objects.

What is Deep Copy

A deep copy is a copying method in Python that creates a completely independent copy of the original object and all its nested objects. Unlike shallow copy, deep copy not only duplicates the top-level elements of the original object, but also recursively duplicates all nested objects within it, such as lists, dictionaries, or custom classes. As a result, any changes made to the copied object or its nested objects will not affect the original object in any way.

To create a deep copy, you can use the copy module’s deepcopy function. For example, consider the following list of lists:

original_list = [[1, 2, 3], [4, 5, 6]]

If you create a deep copy of this list:

import copy
deep_copy = copy.deepcopy(original_list)

The deep_copy will have a new memory address, and any changes made to the top-level elements or nested elements won’t affect the original_list. This is because deep_copy and original_list do not share any objects; they are completely independent of each other.

Deep copies are useful when working with complex data structures or mutable objects where you want to ensure that the original object remains unaffected by any changes made to the copied object. However, deep copying can be slower and more memory-intensive compared to shallow copying, especially for large data structures.

How To Use the Copy Module

The copy module in Python provides two functions for copying objects: copy() for creating shallow copies and deepcopy() for creating deep copies. Here’s how to use the copy module to create both shallow and deep copies of Python objects:

  1. Import the copy module:
import copy
  1. Create a shallow copy using copy.copy():

To create a shallow copy of an object, pass the object to the copy() function. For example, if you want to create a shallow copy of a list:

original_list = [1, 2, 3]
shallow_copy = copy.copy(original_list)

The shallow_copy is a new object with a distinct memory address, but it shares the nested objects with the original_list.

  1. Create a deep copy using copy.deepcopy():

To create a deep copy of an object, pass the object to the deepcopy() function. For example, if you want to create a deep copy of a list of lists:

original_list = [[1, 2, 3], [4, 5, 6]]
deep_copy = copy.deepcopy(original_list)

The deep_copy is a completely independent copy of the original_list, including all its nested objects.

Keep in mind that some objects, like numbers, strings, and tuples, are immutable and do not need to be copied. When you create a shallow or deep copy of an immutable object, you get a reference to the same object because it cannot be modified.

When working with custom classes or more complex data structures, the copy module can still be used to create shallow and deep copies of instances, as long as the class definition supports copying. If needed, you can customize the copying behavior by implementing the __copy__() and __deepcopy__() methods in your class.

How To Create a Shallow Copy

Creating a shallow copy in Python means creating a new object with a distinct memory address, but only duplicating the top-level elements of the original object. For nested objects, the references to those objects are copied, but the nested objects themselves are not duplicated. Here are some ways to create shallow copies in Python:

  1. Using the copy module:

Import the copy module and use the copy() function to create a shallow copy of an object.

import copy

original_list = [1, 2, 3]
shallow_copy = copy.copy(original_list)
  1. Using slicing for lists:

You can create a shallow copy of a list using slicing. This method only works for lists.

original_list = [1, 2, 3]
shallow_copy = original_list[:]
  1. Using the list() constructor:

Another way to create a shallow copy of a list is by using the list() constructor.

original_list = [1, 2, 3]
shallow_copy = list(original_list)
  1. Using the copy() method for dictionaries:

Dictionaries have a built-in copy() method that creates a shallow copy of the dictionary.

original_dict = {'a': 1, 'b': 2, 'c': 3}
shallow_copy = original_dict.copy()

Remember that when you create a shallow copy, any changes made to the top-level elements of the copied object won’t affect the original object.

How To Create a Deep Copy

Creating a deep copy in Python means creating a completely independent copy of the original object and all its nested objects. Unlike shallow copies, deep copies recursively duplicate all nested objects, ensuring that any changes made to the copied object or its nested objects do not affect the original object. Here’s how to create deep copies in Python:

  1. Using the copy module:

Import the copy module and use the deepcopy() function to create a deep copy of an object.

import copy

original_list = [[1, 2, 3], [4, 5, 6]]
deep_copy = copy.deepcopy(original_list)

The deep_copy is a completely independent copy of the original_list, including all its nested objects.

Deep copies are useful when working with complex data structures or mutable objects where you want to ensure that the original object remains unaffected by any changes made to the copied object. However, deep copying can be slower and more memory-intensive compared to shallow copying, especially for large data structures.

When working with custom classes or more complex objects, the copy module’s deepcopy() function can still be used to create deep copies of instances, as long as the class definition supports copying. If needed, you can customize the deep copying behavior by implementing the __deepcopy__() method in your class.

Example of Shallow Copy in Lists

Let’s explore an example of creating a shallow copy of a list containing nested lists:

import copy

# Original list of lists
original_list = [['a', 'b', 'c'], [1, 2, 3]]

# Creating a shallow copy using the copy module
shallow_copy = copy.copy(original_list)

# Modifying the top-level element of the shallow copy
shallow_copy[0] = ['x', 'y', 'z']

# Modifying a nested element in the shallow copy
shallow_copy[1][0] = 42

print("Original list:", original_list)
print("Shallow copy:", shallow_copy)

Output:

Original list: [['a', 'b', 'c'], [42, 2, 3]]
Shallow copy: [['x', 'y', 'z'], [42, 2, 3]]

In this example, the top-level elements of the shallow_copy are different from those in the original_list. Changing the top-level element in the shallow_copy did not affect the original_list. However, the nested objects within the lists are shared between the original and the shallow copy. When we modified the nested element in the shallow_copy, the change was also reflected in the original_list. This demonstrates the behavior of shallow copies in lists.

Example of Shallow Copy in Dictionaries

Let’s explore an example of creating a shallow copy of a dictionary containing nested dictionaries:

import copy

# Original dictionary with nested dictionaries
original_dict = {
    'A': {'a': 1, 'b': 2},
    'B': {'c': 3, 'd': 4}
}

# Creating a shallow copy using the copy module
shallow_copy = copy.copy(original_dict)

# Modifying the top-level element of the shallow copy
shallow_copy['A'] = {'e': 5, 'f': 6}

# Modifying a nested element in the shallow copy
shallow_copy['B']['c'] = 42

print("Original dictionary:", original_dict)
print("Shallow copy:", shallow_copy)

Output:

Original dictionary: {'A': {'a': 1, 'b': 2}, 'B': {'c': 42, 'd': 4}}
Shallow copy: {'A': {'e': 5, 'f': 6}, 'B': {'c': 42, 'd': 4}}

In this example, the top-level elements of the shallow_copy are different from those in the original_dict. Changing the top-level element in the shallow_copy did not affect the original_dict. However, the nested dictionaries within the dictionaries are shared between the original and the shallow copy. When we modified the nested element in the shallow_copy, the change was also reflected in the original_dict. This demonstrates the behavior of shallow copies in dictionaries.

Example of Deep Copy in Lists

Let’s explore an example of creating a deep copy of a list containing nested lists:

import copy

# Original list of lists
original_list = [['a', 'b', 'c'], [1, 2, 3]]

# Creating a deep copy using the copy module
deep_copy = copy.deepcopy(original_list)

# Modifying the top-level element of the deep copy
deep_copy[0] = ['x', 'y', 'z']

# Modifying a nested element in the deep copy
deep_copy[1][0] = 42

print("Original list:", original_list)
print("Deep copy:", deep_copy)

Output:

Original list: [['a', 'b', 'c'], [1, 2, 3]]
Deep copy: [['x', 'y', 'z'], [42, 2, 3]]

In this example, both the top-level elements and the nested elements of the deep_copy are different from those in the original_list. Changing the top-level element and the nested element in the deep_copy did not affect the original_list. This demonstrates the behavior of deep copies in lists, where the original and copied objects are completely independent, including all their nested objects.

Example of Deep Copy in Dictionaries

Let’s explore an example of creating a deep copy of a dictionary containing nested dictionaries:

import copy

# Original dictionary with nested dictionaries
original_dict = {
    'A': {'a': 1, 'b': 2},
    'B': {'c': 3, 'd': 4}
}

# Creating a deep copy using the copy module
deep_copy = copy.deepcopy(original_dict)

# Modifying the top-level element of the deep copy
deep_copy['A'] = {'e': 5, 'f': 6}

# Modifying a nested element in the deep copy
deep_copy['B']['c'] = 42

print("Original dictionary:", original_dict)
print("Deep copy:", deep_copy)

Output:

Original dictionary: {'A': {'a': 1, 'b': 2}, 'B': {'c': 3, 'd': 4}}
Deep copy: {'A': {'e': 5, 'f': 6}, 'B': {'c': 42, 'd': 4}}

In this example, both the top-level elements and the nested elements of the deep_copy are different from those in the original_dict. Changing the top-level element and the nested element in the deep_copy did not affect the original_dict. This demonstrates the behavior of deep copies in dictionaries, where the original and copied objects are completely independent, including all their nested objects.

How To Choose Between Shallow and Deep Copy

When deciding whether to use a shallow copy or a deep copy in Python, consider the following factors:

  1. Complexity of the data structure:

If your data structure only contains simple or immutable objects (e.g., numbers, strings, or tuples) without any nested objects, a shallow copy is usually sufficient. However, if your data structure contains mutable objects with nested objects (e.g., lists or dictionaries), you may need to choose between shallow and deep copies depending on your specific requirements.

  1. Modifications to copied objects:

If you need to modify the copied object without affecting the original object, including their nested objects, a deep copy is the best choice. A deep copy ensures that the copied object and the original object are completely independent of each other.

On the other hand, if you only need to modify the top-level elements of the copied object without affecting the original object, and it’s acceptable for the nested objects to be shared between the original and the copied object, a shallow copy is more suitable. A shallow copy is also faster and uses less memory compared to a deep copy.

  1. Performance and memory usage:

Shallow copies are generally faster and consume less memory than deep copies, as they only duplicate the top-level elements of the object. If performance and memory usage are critical concerns for your application, and you don’t need completely independent copies of your objects, consider using shallow copies.

Deep copies can be slower and more memory-intensive, especially for large or complex data structures, as they recursively duplicate all nested objects. Use deep copies when it’s essential to create completely independent copies of the original object and all its nested objects.

In summary, when choosing between a shallow and a deep copy, carefully consider the complexity of your data structure, the intended modifications to the copied object, and the performance and memory requirements of your application.

Click to share! ⬇️