Shallow and deep copy in Python: copy(), deepcopy()

Modified: | Tags: Python

In Python, you can make a shallow and deep copy using the copy() and deepcopy() functions from the copy module. A shallow copy can also be made with the copy() method of lists, dictionaries, and so on.

The following is a summary of the differences between assignment to another variable, shallow copy, and deep copy.

import copy

l = [0, 1, [2, 3]]
l_assign = l                   # assignment
l_copy = l.copy()              # shallow copy
l_deepcopy = copy.deepcopy(l)  # deep copy

l[1] = 100
l[2][0] = 200
print(l)
# [0, 100, [200, 3]]

print(l_assign)
# [0, 100, [200, 3]]

print(l_copy)
# [0, 1, [200, 3]]

print(l_deepcopy)
# [0, 1, [2, 3]]

Shallow copy and deep copy in Python

The Python official documentation describes shallow copy and deep copy as follows:

The difference between shallow and deep copying is only relevant for compound objects (objects that contain other objects, like lists or class instances):

When objects are contained within mutable objects, like elements in a list or values in a dictionary, a shallow copy creates references to the original objects, while a deep copy creates new copies of the original objects. With references, the elements point to the same object, so modifying one of them also affects the other.

Assignment to another variable

First, let's explore what happens when assigning an object to another variable.

When a mutable object, such as a list or a dictionary, is assigned to multiple variables, updating one variable (e.g., changing, adding, or deleting elements) will also affect the other variables.

l1 = [0, 1, [2, 3]]
l2 = l1

print(l1 is l2)
# True

l1[1] = 100
l1[2][0] = 200
print(l1)
# [0, 100, [200, 3]]

print(l2)
# [0, 100, [200, 3]]

print(l1 is l2)
# True

As demonstrated in the above code example, the is operator shows that the two variables refer to the same object both before and after the value change.

To create a copy instead of a reference of the same object, use the copy() method or the copy.copy() and copy.deepcopy() functions described below.

For immutable objects like numbers (int, float) and strings (str), they cannot be updated. When these types of objects are assigned, the two variables refer to the same object initially. However, if one variable is updated to a new value, it becomes a separate object, and the other variable remains the same.

i1 = 1
i2 = i1

print(i1 is i2)
# True

i1 += 100
print(i1)
# 101

print(i2)
# 1

print(i1 is i2)
# False

Shallow copy: copy(), copy.copy(), etc.

copy() method of list, dictionary, etc.

The copy() method is provided for lists, dictionaries, etc. The copy() method makes a shallow copy.

A shallow copy creates a new object that contains references to the elements found in the original object. For example, in the case of a list, the copied list is a new object, but its elements are references to the same objects in the original list.

l = [0, 1, [2, 3]]
l_copy = l.copy()

print(l is l_copy)
# False

print(l[2] is l_copy[2])
# True

Therefore, if the elements are mutable, updating one also affects the other. However, in the case of an immutable element, when its value is changed, it becomes a separate object. The corresponding element in the other list remains unchanged.

l[1] = 100
l[2][0] = 200
print(l)
# [0, 100, [200, 3]]

print(l_copy)
# [0, 1, [200, 3]]

print(l[2] is l_copy[2])
# True

The same applies not only to a list of lists as in the example above, but also to a list of dictionaries, a nested dictionary (a dictionary containing other dictionaries as values), and so on.

Slice

Slices for mutable sequence types, such as lists, also make shallow copies.

For example, applying the slice [:] that specifies all elements makes a shallow copy of the whole list.

l = [0, 1, [2, 3]]
l_whole_slice = l[:]

l[1] = 100
l[2][0] = 200
print(l)
# [0, 100, [200, 3]]

print(l_whole_slice)
# [0, 1, [200, 3]]

Before Python 3.3 introduced the copy() method for lists, the [:] notation was commonly used to create a shallow copy. For new code, it is generally recommended to use the copy() method to make your intentions clearer.

A slice of a part of the sequence also creates a shallow copy.

l = [0, 1, [2, 3]]
l_slice = l[1:]
print(l_slice)
# [1, [2, 3]]

l[1] = 100
l[2][0] = 200
print(l)
# [0, 100, [200, 3]]

print(l_slice)
# [1, [200, 3]]

If you need to make a deep copy, you can apply the copy.deepcopy() function to the slice.

list(), dict(), etc.

You can make a shallow copy of a list or dictionary by passing the original list or dictionary to list() or dict().

l = [0, 1, [2, 3]]
l_list = list(l)

l[1] = 100
l[2][0] = 200
print(l)
# [0, 100, [200, 3]]

print(l_list)
# [0, 1, [200, 3]]

copy.copy()

You can also make a shallow copy with the copy() function from the copy module.

import copy

l = [0, 1, [2, 3]]
l_copy = copy.copy(l)

l[1] = 100
l[2][0] = 200
print(l)
# [0, 100, [200, 3]]

print(l_copy)
# [0, 1, [200, 3]]

Use copy.copy() when you need to create a shallow copy of an object that does not provide a copy() method.

Deep copy: copy.deepcopy()

To make a deep copy, use the deepcopy() function from the copy module.

import copy

l = [0, 1, [2, 3]]
l_deepcopy = copy.deepcopy(l)

print(l is l_deepcopy)
# False

print(l[2] is l_deepcopy[2])
# False

l[1] = 100
l[2][0] = 200
print(l)
# [0, 100, [200, 3]]

print(l_deepcopy)
# [0, 1, [2, 3]]

In a deep copy, actual copies of the objects are inserted instead of their references. As a result, changes to one object do not affect the other.

Here's an example of applying the deepcopy() function to a slice:

l = [0, 1, [2, 3]]
l_slice_deepcopy = copy.deepcopy(l[1:])
print(l_slice_deepcopy)
# [1, [2, 3]]

l[1] = 100
l[2][0] = 200
print(l)
# [0, 100, [200, 3]]

print(l_slice_deepcopy)
# [1, [2, 3]]

Related Categories

Related Articles