This is because you are creating only one inner list object, and altering it.
In pseudocode, what you are doing is:
- Create a list called
line
assigning [None, None, None]
to it
- Create an empty list called
lines
- For three times:
-- Pick n items from the square
list
-- Assign these three items to line[0]
, line[1]
and line[2]
-- Append line
to lines
So, what you are doing is assigning to individual items of line
. This is important - you're not making a new object each time, you're changing individual items in the line
list.
At the end of it all, line
will point to the list [7, 8, 9]
. And you can see lines
as being substantially [line, line, line]
(a list of three times the same object), so specifically now it will point to [[7,8,9], [7,8,9], [7,8,9]]
.
To solve this, possibly the solution that most keeps your original code is to re-define line
after appending it. This way, the variable name line
will refer to a different list each time, and you won't have this problem.
def getLines(square, N):
i = 0
line = [None]*N
lines = list()
for elt in square:
line[i] = elt
i += 1
if i == N:
lines.append(line)
line = [None]*N # Now `line` points to a different object
i = 0
return lines
Of course, there is leaner, more Pythonic code that can do the same thing (I see that an answer has already been given).
EDIT - Ok, here goes a somehow more detailed explanation.
Perhaps one of the key concepts is that lists are not containers of other objects; they merely hold references to other objects.
Another key concept is that when you change an item in a list (item assignment), you're not making the whole list object become another object. You're merely changing a reference inside it. This is something we give for granted in a lot of situations, but somehow becomes counter-intuitive when we'd want things to go the other way and "recycle" a list.
As I was writing in the comments, if list
was a cat named Fluffy, every time you're appending you're creating a mirror that points to Fluffy. So you can dress Fluffy with a party hat, put a mirror pointing to it, then give Fluffy a clown nose, put on another mirror, then dress Fluffy as a ballerina, add a third mirror, and when you look at the mirrors, all three of them will show the ballerina Fluffy. (Sorry Fluffy).
What I mean is that in practice in your first script, when you do the append:
lines.append(line)
by the first concept I mentioned, you are not making lines
contain the current status of line
as a separate object. You are appending a reference to the line
list.
And when you do,
line[i] = elt
by the second concept, of course line
is always the same object; you're just changing what's referenced at the i-th position.
This is why, at the end of your script, lines
will appear to "contain three identical objects": because you actually appended three references to the same object. And when you ask to see the content of lists
, you will read, three times, the list
object in its current status.
In the code I provided above, I re-define the name lists
to make it reference a brand new list every time it's been appended to lists
:
lines.append(line)
line = [None]*N # Now `line` points to a different object
This way, at the end of the script I have "three different cats" appended, and each one was conveniently named Fluffy just until I had appended it, to give room for a new Fluffy list after that.
Now, in your second script, you do something similar. The key instruction is:
lines = [[None]*N]*N # Need to be initialized to be able to index it.
In this line, you are creating two objects:
- the list [None, None, None]
- the list named lines
, which contains N references to the same list [None, None, None]
.
What you did was just to create straight away Fluffy and the three mirrors pointing at him.
In fact if you change lines[0][2]
, or lines[1][2]
, you're just changing the same item [2] of your same Fluffy.
What you actually wanted to do is,
lines = [[None]*N for i in range(N)]
which creates three different cats - I mean, lists, and have lines
point to the three.