Shadow copy and deep copy in Python

In this post, we’ll try to understand the difference between shadow copy and deep copy in Python. We study the copy behavior with list data structure in Python.

Problem statement

Assuming we have the following list:

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

We want to create a duplicate list so that we can modify the contents independently without affecting the original list.

test_list_clone = [1, 2, [3, 6], 4, 5]
test_list_clone[0] = 11
test_list_clone[2].append(11)
print(test_list)
print(test_list_clone)

The expected output should be:

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

[11, 2, [3, 6, 11], 4, 5]

How should we copy the original list?

Duplicate reference

It’s not uncommon that you may want to copy the list as below.

test_list_clone = test_list
test_list_clone[0] = 11
test_list_clone[2].append(11)
print(test_list)
print(test_list_clone)

Output:

[11, 2, [3, 6, 11], 4, 5]

[11, 2, [3, 6, 11], 4, 5]

This doesn’t work since it simply stores the list reference to another variable. When the duplciate list is modified, the original list is also modified.

Copy list by brute force

In this solution, we append each item in the original list to the duplicate list by using a list comprehensions(a short form of for loop).

test_list_clone = [item for item in test_list]
test_list_clone[0] = 22
test_list_clone[2][-1] = 22
print(test_list)
print(test_list_clone)

Output:

[11, 2, [3, 6, 22], 4, 5]

[22, 2, [3, 6, 22], 4, 5]

This works partially but not for the nested list. This is because it only duplicates the outer list. But the inner list is still duplicated as a reference of original object. That’s why the number 22 is appended to both the duplciate list and original list.

Copy list with slice

We can get a subset of the list with slice in Python. The colon means to get the whole copy as original. Again, this only duplicates the outer list.

test_list_clone = test_list[:]
test_list_clone[0] = 33
test_list_clone[2][-1] = 33
print(test_list)
print(test_list_clone)

Output:

[11, 2, [3, 6, 33], 4, 5]

[33, 2, [3, 6, 33], 4, 5]

Copy list with list constructor

A more readable form might be using list constructor to duplciate the list. But it has the same issue without deep copy of inner list.

test_list_clone = list(test_list)
test_list_clone[0] = 44
test_list_clone[2][-1] = 44
print(test_list)
print(test_list_clone)

Output:

[11, 2, [3, 6, 44], 4, 5]

[44, 2, [3, 6, 44], 4, 5]

Copy list with starred expression

Another way is to use starred expression to duplciate the list.

test_list_clone = [*test_list]
test_list_clone[0] = 55
test_list_clone[2][-1] = 55
print(test_list)
print(test_list_clone)

Output:

[11, 2, [3, 6, 55], 4, 5]

[55, 2, [3, 6, 55], 4, 5]

Copy list with copy function, python 3.3+

In Python 3.3+, a copy function of the list is available. But it doesn’t do the deep copy either.

test_list_clone = test_list.copy()
test_list_clone[0] = 66
test_list_clone[2][-1] = 66
print(test_list)
print(test_list_clone)

Output:

[11, 2, [3, 6, 66], 4, 5]

[66, 2, [3, 6, 66], 4, 5]

Copy list with multiplication

A tricky way to duplciate the list is to use multiplication style.

test_list_clone = test_list * 1
test_list_clone[0] = 77
test_list_clone[2][-1] = 77
print(test_list)
print(test_list_clone)

Output:

[11, 2, [3, 6, 77], 4, 5]

[77, 2, [3, 6, 77], 4, 5]

Copy list with copy package

Fortunately, a copy package is built in Python to do the shadow copy and deep copy for us.

Shadow copy

import copy

test_list_clone = copy.copy(test_list)
test_list_clone[0] = 88
test_list_clone[2][-1] = 88
print(test_list)
print(test_list_clone)

Output:

[11, 2, [3, 6, 88], 4, 5]

[88, 2, [3, 6, 88], 4, 5]

Deep copy

test_list_clone = copy.deepcopy(test_list)
test_list_clone[0] = 99
test_list_clone[2][-1] = 99
print(test_list)
print(test_list_clone)

Output:

[11, 2, [3, 6, 88], 4, 5]

[99, 2, [3, 6, 99], 4, 5]

Finally, we got a way to do the deep copy for the nested list. Now, the nested list is modified without affecting the original list content.