Copying Objects (Optional Section)

4.4. Copying Objects (Optional Section)#

Aliasing in Python can make a program harder to understand and maintain because changes to one variable may have unexpected effects on other variables that refer to the same object. To avoid aliasing and create independent copies of objects, you can use the copy module, which provides a function called copy to duplicate objects [Downey, 2015, Python Software Foundation, 2023].

Let’s illustrate this with an example using the Point class:

# Define the Point class and the print_point function (previously defined)
class Point:
    def __init__(self):
        self.x = 0
        self.y = 0

# Create an instance of Point and set its attributes
p1 = Point()
p1.x = 3.0
p1.y = 4.0

# Import the copy module and create a copy of p1
import copy
p2 = copy.copy(p1)

In the example above, we create two instances of the Point class: p1 and p2. Then, we use copy.copy() to create a shallow copy of p1, which we assign to p2. A shallow copy creates a new object but does not recursively copy objects contained within it.

When comparing p1 and p2, we find that they are different objects:

print(p1 is p2)  # Output: False
False

However, since copy.copy() creates a shallow copy, the Point objects inside p1 and p2 are still the same:

print(p1 == p2)  # Output: False
print(p1.x is p2.x)  # Output: True
False
True

As a result, changes to p2.x will also affect p1.x, since they refer to the same Point object.

It’s important to note that the behavior of == for programmer-defined types (like the Point class) is the same as the is operator by default, checking object identity rather than object equivalence. If you want to define custom behavior for the == operator, you need to override the __eq__() method in your class to specify how instances should be considered equivalent.

Summary

In summary, when using copy.copy(), be aware that it creates a shallow copy, and changes made to mutable objects inside the copied object will affect the original object as well. If you need a deep copy, which duplicates all nested objects, you can use copy.deepcopy() from the copy module instead.

In Python, copy and deepcopy are two different methods used to create copies of objects, but they behave differently when dealing with nested or compound objects. Let’s see the difference between them:

4.4.1. Shallow Copy (copy)#

The copy method creates a new object that is a shallow copy of the original object. A shallow copy only copies the top-level object and does not create copies of the objects inside it. Instead, it creates references to the same objects that exist in the original object. If the original object contains mutable objects (e.g., lists or dictionaries), changes made to these mutable objects inside the copied object will also affect the original object. Shallow copy is typically created using the copy module’s copy() function or the object’s own copy() method.

Example:

import copy
original_list = [1, 2, [3, 4]]
shallow_copy_list = copy.copy(original_list)

print(shallow_copy_list)       # Output: [1, 2, [3, 4]]

# Modify the nested list in the shallow copy
shallow_copy_list[2][0] = 99

print(shallow_copy_list)       # Output: [1, 2, [99, 4]]
print(original_list)           # Output: [1, 2, [99, 4]] (Original list also changed)
[1, 2, [3, 4]]
[1, 2, [99, 4]]
[1, 2, [99, 4]]

4.4.2. Deep Copy (deepcopy)#

The deepcopy method, also from the copy module, creates a new object that is a deep copy of the original object. Unlike a shallow copy, a deep copy creates completely independent copies of all the objects inside the original object. Changes made to the objects inside the copied object will not affect the original object, and vice versa. Deep copy is useful when you want to create a copy that is entirely independent of the original object, including all the nested objects.

Example:

import copy
original_list = [1, 2, [3, 4]]
deep_copy_list = copy.deepcopy(original_list)

print(deep_copy_list)       # Output: [1, 2, [3, 4]]

# Modify the nested list in the deep copy
deep_copy_list[2][0] = 99

print(deep_copy_list)       # Output: [1, 2, [99, 4]]
print(original_list)        # Output: [1, 2, [3, 4]] (Original list remains unchanged)
[1, 2, [3, 4]]
[1, 2, [99, 4]]
[1, 2, [3, 4]]