Python, pitfall of creating 2d list, [foo*3]*3
When trying to create a 2d list, I used to write in this way since it looks simple and easy.
a = [12*3]*3
I didn’t realize its undesirable side effect until solving one problem yesterday.
What is problem ?
if we are trying to change value of one element, the corresponding elem in each row will be modified.
a[0][0] = 10
The expected output should be
[ [10, 12, 12], [12, 12, 12], [12, 12, 12] ]
However, It actually becomes
[ [10, 12, 12], [10, 12, 12], [10, 12, 12] ]
The reason
1-D list
An experience is made to explain this problem. Here is a case for 1d list
a = [12]*5
print id(a)
print id(a[0]), id(a[1]), id(a[4])
a[0] = 10
print a
print id(a)
print id(a[0]), id(a[1]), id(a[4])
output is
4148536204
141234284 141234284 141234284
[10, 12, 12, 12, 12]
4148536204
141234308 141234284 141234284
We notice that even if all elem in list have the same id, python will create a new object for a coming value because integer is immutable ( id(a[0]), id(a[1]) are different ).
2-D list
a = [[12]*3]*3
print id(a)
print id(a[0]), id(a[0][0]), id(a[0][1]), id(a[0][2])
print id(a[1]), id(a[1][0]), id(a[1][1]), id(a[1][2])
print id(a[2]), id(a[2][0]), id(a[2][1]), id(a[2][2])
print a
a[0][0] = 10
print a
print id(a)
print id(a[0]), id(a[0][0]), id(a[0][1]), id(a[0][2])
print id(a[1]), id(a[1][0]), id(a[1][1]), id(a[1][2])
print id(a[2]), id(a[2][0]), id(a[2][1]), id(a[2][2])
output is
4147959532
4147958668 152936556 152936556 152936556
4147958668 152936556 152936556 152936556
4147958668 152936556 152936556 152936556
[[12, 12, 12], [12, 12, 12], [12, 12, 12]]
[[10, 12, 12], [10, 12, 12], [10, 12, 12]]
4147959532
4147958668 152936580 152936556 152936556
4147958668 152936580 152936556 152936556
4147958668 152936580 152936556 152936556
The picture for illustrating this problem is shown below
Although the list(id=4147958668) has been modified, all first elem in each list in 2D list are pointing to the first elem in list(id=4147958668). That is why all of them become 10.
How to solve it ?
Method 1
a = [x[:] for x in [[12]*3]*3]
Test
a = [[12]*3]*3
print id(a)
print id(a[0]), id(a[0][0]), id(a[0][1]), id(a[0][2])
print id(a[1]), id(a[1][0]), id(a[1][1]), id(a[1][2])
print id(a[2]), id(a[2][0]), id(a[2][1]), id(a[2][2])
print a
a = [x[:] for x in [[12]*3]*3]
print id(a)
print id(a[0]), id(a[0][0]), id(a[0][1]), id(a[0][2])
print id(a[1]), id(a[1][0]), id(a[1][1]), id(a[1][2])
print id(a[2]), id(a[2][0]), id(a[2][1]), id(a[2][2])
Output is
4148614892
4148614028 158683244 158683244 158683244
4148614028 158683244 158683244 158683244
4148614028 158683244 158683244 158683244
[[12, 12, 12], [12, 12, 12], [12, 12, 12]]
4148613388
4148615500 158683244 158683244 158683244
4148615276 158683244 158683244 158683244
4148615308 158683244 158683244 158683244
If we use this method, we find a[0], a[1], a[2] have different addresses. The picture is shown below
Mothod 2
a = [[12 for x in range(3)] for y in range(3)]
Test
a = [[12 for x in range(3)] for y in range(3)]
print id(a)
print id(a[0]), id(a[0][0]), id(a[0][1]), id(a[0][2])
print id(a[1]), id(a[1][0]), id(a[1][1]), id(a[1][2])
print id(a[2]), id(a[2][0]), id(a[2][1]), id(a[2][2])
Output is
4148420908
4148422412 143655020 143655020 143655020
4148420876 143655020 143655020 143655020
4148423308 143655020 143655020 143655020
It’s the same method as method 1
Summary
The correct way to create 2D list are
python
a = [x[:] for x in [[12]*3]*3]
a = [[12 for x in range(3)] for y in range(3)]
a = [ [12]*3 for dummy in range(3) ]
the third one is in my favorite one.