Attempt 1
>>> width = 3
>>> height = 2
>>> zeros = [[0]*width]*height
>>> print(zeros)
[[0, 0, 0], [0, 0, 0]]
>>> zeros[1][2] = "foo"
>>> print(zeros)
[[0, 0, 'foo'], [0, 0, 'foo']]
Expected result was [[0, 0, 0], [0, 0, 'foo']]
. It seems that the *
operator populated each row with references to the same object, so modifying zeros[1]
also modified zeros[0]
.
Attempt 2
So now we create the initial list object using list comprehension instead:
>>> zeros = [[0 for i in range(width)] for j in range(height)]
>>> print(zeros)
[[0, 0, 0], [0, 0, 0]]
>>> zeros[1][2] = "foo"
>>> print(zeros)
[[0, 0, 0], [0, 0, 'foo']]
Great, this is the expected result.
The question is...
If the *
operator populates each row with references to the same object, then why doesn't every element within each row refer to the same object?
That is, with a (partial) understanding of why Attempt 1 didn't produce the expected output, I would expect that if ANY element of zeros
is modified, ALL elements would be changed, that is:
>>> zeros = [[0]*width]*height
>>> print(zeros)
[[0, 0, 0], [0, 0, 0]]
>>> zeros[1][2] = "foo"
>>> print(zeros)
is expected to return:
>>> print(zeros)
[['foo', 'foo', 'foo'], ['foo', 'foo', 'foo']]
but instead returns:
>>> print(zeros)
[[0, 0, 'foo'], [0, 0, 'foo']]
So why does the second *
operator populate each row with references to the same object, but each of the elements WITHIN any given row are unique objects?
Answer
List of lists changes reflected across sublists unexpectedly
tl;dr:
Integers are immutable, so [0]*3
creates copies the elements of [0], not references. However [0,0,0] is an object, so [0,0,0]*2
creates a new reference to the original object.