Here is the classical case of aliasing:
L1 = [1, 2, 3]
L2 = L1
L2[0] = 5
print(L1)
print(L2)
Like we saw before L1 and L2 refer to the same list in memory, so modifying thecontents of one means modifying the contents of the other.
Suppose we want to avoid that. In order to do that, we'd like to create a copy of L1: alist stored at a different location in memory that has the same contents.
Here is the simplest way to achieve this:
L1 = [1, 2, 3]
L2 = [L1[0], L1[1], L1[2]]
This creates a new list whose contents are L1[0], L1[1], L1[2]. Modifying L1 will now not affect L2.
Here is another way to do this:
L1 = [1, 2, 3]
L2 = L1[:]
L1[:] creates a new list whose contents are L1[0], L1[1], ..., L[len(L)-1], and places it in memory. We then assign the address of the new list to L2. (Review slicing notation to understand L[:]; slicing always creates a new list.)
Finally, we could accomplish the same thing using a loop.
L1 = [1, 2, 3]
L2 = []
for e in L1:
L2.append(e)
Here, we repeatedly appended elements of L1 to the newly-created list L2, which is initially just empty.
What we did above is called making a shallow copy. Here is why. Suppose we have a nested list, and try to make a copy of it.
L1 = [[1, 2], [3, 4]]
L2 = L1[:] #the same as L2 = [L1[0], L1[1]]
The problem is that we only have one of the list [1, 2], and one of the list [3, 4] placed in memory. When we create L2, it's separate from L1, but L1[0] and L2[0] are aliases (and also L1[1] and L2[1] are aliases) -- we never created copies of L1[0] and L1[1].
That means that modifying the contents of the inner lists of L1 will have an effect on the inner lists of L2 -- since they are aliases of each other!
L1 = [[1, 2], [3, 4]]
L2 = L1[:] #the same as L2 = [L1[0], L1[1]]
L1[1][0] = 7
print("L1 = ", L1)
print("L2 = ", L2)
On the other hand, since L1 and L2 are not aliases of each other, modifying L1[0] won't effect L2[0]:
L1 = [[1, 2], [3, 4]]
L2 = L1[:] #the same as L2 = [L1[0], L1[1]]
L1[0] = [5, 6]
print("L1 = ", L1)
print("L2 = ", L2)
We say the copy is shallow because the inner nested lists are still aliases of each other.
Here is the least flexible way to make a deep copy:
L1 = [[1, 2], [3, 4]]
L2 = [[L1[0][0], L1[0][1]], [L1[1][0], L1[1][1]]]
L1[1][0] = 7
print("L1 = ", L1)
print("L2 = ", L2)
The lists are completely disentagled (of course, the id's of equal integers are equal, but the contents of integers cannot be changed, so that's not a problem.)