Deep Copy vs. Shallow CopyΒΆ

Here's a brief review of Shallow Copy:

In [1]:
L1 = [[1, 2], [3, 4]]
L2 = L1[:]

Here, L1[0] and L2[0] are aliases. Changing the contents of L1[0] (e.g. with L1[0][0] = 5) will also change the contents of L2[0] and vice versa. To address this, we can instead create a deep copy of L1:

In [2]:
copy = []
for e in L1:
    copy.append(e[:])

Here, we are creating a new list every time we append, since we are appending a copy of e, not e itself. However, this only works if L1 happens to be a list of lists of ints. For example, this would create a problem:

In [3]:
L1 = [[1, 2], [3, [4, 5]]]
copy = []
for e in L1:
    copy.append(e[:])

We are not creating a copy of [4, 5] here, only a copy of [3, [4, 5]. That means that L1[1][1] and copy[1][1] will be alises. Changing one will change the other.

If, instead of appending the shallow copy of e (i.e., e[:]), we were to append the deep copy of e, things would work. We use a recursive solution to accomplish this.

(Side note: type(obj) returns the type of obj. For example, type([1, 2, 3]) == list is True.

In [4]:
def deep_copy(obj):
    '''Return a deep (i.e., non-aliased at all) copy of obj, a nested list of 
    integers'''
    #Base case:
    if type(obj) != list:
        return obj
    copy = []
    for elem in obj:
        #The leap of faith: assume that deep_copy works!
        copy.append(deep_copy(elem))
    return copy

The base case is easy: if obj is not a list, then it's an integer, and we consider ints to be their own deep copies. Otherwise, we perform the recursive leap of faith: we append to copy the deep copies of each of obj's elements. If we assume deep_copy works, then the idea works as a whole.