Chapter 9 Lists
9.9 Copying Lists¶
Aliasing. Since variables refer to objects, if a variable is assigned to another, both variables refer to the same object:
In this case, the reference diagram looks like this:
Because the same list has two different names, a
and b
, it is called
aliased. Changes made with one alias affect the other. In the codelens example below, you can see that a
and b
refer
to the same list after executing the assignment statement b = a
.
Although this behavior can be useful, it is sometimes unexpected or undesirable. In general, it is safer to avoid aliasing when you are working with mutable objects. Of course, for immutable objects, there’s no problem. That’s why Python is free to alias strings and integers when it sees an opportunity to economize.
There are situations where a copy of a list object is needed. For example, when a modified version of a list is requested, but the original list must be kept for further uses, a copy of the original list can be made for the modified version. There are two ways to obtain a copy of a list object, namely the list slicing operation and the copy module.
The getslice operation for a list object makes a copy of a slice from the list object, and returns the copy rather than a reference to the original slice. For example,
>>> l = [1, 3, 5, 7, 9]
>>> x = l[2:4]
>>> x
[5, 7]
>>> x[0] = 0
>>> x
[0, 7]
>>> l
[1, 3, 5, 7, 9]
In the example above, the value of l does not change when the value of x changes, because x and l do not have direct binding relationships.
A simple way of copying a list is to slice the whole list. When both the start and the end indices are omitted in a getslice operation, the whole sequence is taken as the resulting slice. This provides a more succinct way of copying a list:
>>> l = [1, 3, 5, 7, 9]
>>> l1 = l[0:len(l)] # the whole list sliced and copied
>>> l2 = l[:] # a more succinct way of slicing a list
>>> l[0] = 0
>>> l1[1] = 0
>>> l2[2] = 0
>>> l
[0, 3, 5, 7, 9]
>>> l1
[1, 0, 5, 7, 9]
>>> l2
[1, 3, 0, 7, 9]
As shown by the example above, modifications to l, l1 and l2 do not affect each other, because they are different copies of the same list object.
A second way of making a copy of a list object is using the copy module. The module contains a function, copy, which takes a single argument, which is typically a mutable container object, and returns a copy of the argument.
>>> import copy
>>> l = [1, 3, 5, 7, 9]
>>> l1 = copy.copy(l)
>>> l[0] = 0
>>> l
[0, 3, 5, 7, 9]
>>> l1
[1, 3, 5, 7, 9]
For an example where list copying is used, consider the problem of deciding whether an integer is a palindrome number. A palindrome number is a number that remains the same when the order of the digits in it is reversed. For example, 12321 and 1111 are palindrome numbers, but 123 is not a palindrome number. The function palindrome below takes an integer argument and returns a Boolean value indicating whether the argument is a palindrome number.
The palindrome function above works by converting the integer argument into a string, so that each digit is converted into a character. Then it converts the string into a list of all the digits in the integer. The digits are put into the reverse order by making a copy of the list and reversing it. A comparison between the reversed version and the original list determines whether the integer is a palindrome number.
Given that the slicing operation l*[:: −1] makes a reverse copy of the *list, the palindrome function can be simplified as:
Further given that the slicing operation applies to strings also, the function can be further simplified, without using a list.
When making a copy of a nested list, the sub lists are shared by the two copies.
>>> l1 =[[1 ,2 ,3], 4, 5]
>>> import copy
>>> l2=copy.copy(l1) #or l2=l1[:]
>>> l1[2]=6 # l1 changes first level
>>> l1
[[1 ,2 ,3], 4, 6] # l1 change is seen in first level
>>> l2
[[1 ,2 ,3], 4, 5] # l2 does not change
>>> l1[0][2]=6 # l1 changes second level
>>> l1
[[1 ,2 ,13], 4, 6] # l1 change is seen in second level
>>> l2
[[1 ,2 ,13], 4, 5] # l2 change is also seen in second level
As can be seen from the example above, a copy of a nested list can lead to unexpected common changes in both copies. To avoid this, the function copy.deepcopy can be used to copy nested lists recursively. The syntax is the same as copy.copy.
>>> l1 =[[1 ,2 ,3], 4, 5]
>>> import copy
>>> l2=deep.copy(l1) #or l2=l1[:]
>>> l1[2]=6 # l1 changes first level
>>> l1
[[1 ,2 ,3], 4, 6] # l1 change is seen in first level
>>> l2
[[1 ,2 ,3], 4, 5] # l2 does not change
>>> l1[0][2]=6 # l1 changes second level
>>> l1
[[1 ,2 ,13], 4, 6] # l1 change is seen in second level
>>> l2
[[1 ,2 ,3], 4, 5] # l2 does not change
As shown in the example above, l2 and l1 are fully independent after copy.deepcopy. When copying a nested list, deep copying is preferred.
The underlying mechanisms of copying and deep copying. In the copy.copy example above, l1 and l2 share reference to the same list object which contains items 1, 2 and 3. Their memory structures (in simplified form) are illustrated in Fig. 9.6(a).
After l1’s changes, the result is shown in Fig. 9.6(b).
In order to copy the sublist inside the list, the function copy.deepcopy can be used instead of copy.copy. copy.deepcopy makes a deep copy of a container object by recursively following binding links inside the container. Following the binding links recursively, all reachable objects in the container object are copied (see Fig. 9.7(a)). So when l1’s sublist is modified, l2’s sublist does not changes as illustrated in Fig. 9.7(b).
Check your understanding
- [4, 2, 8, 6, 5]
- blist is not a copy of alist, it is a reference to the list alist refers to.
- [4, 2, 8, 999, 5]
- Yes, since alist and blist both reference the same list, changes to one also change the other.
9.9Q-1: What is printed by the following statements?
alist = [4, 2, 8, 6, 5]
blist = alist
blist[3] = 999
print(alist)
© Copyright 2024 GS Ng.