Two-dimensional list wrongly assigning values in p

2019-06-22 01:52发布

问题:

class Board:

    def __init__(self):
        self.board = self.createBoard()

    def createBoard(self):
        line = []
        for i in range(7):
            line.append(' ')
        board = []
        for i in range(7):
            board.append(line)
        return board

    def showBoard(self):
        line = "| "
        for x in range(len(self.board)):
            for y in range(len(self.board)):
                line += self.board[x][y] + " | "
            print("-" * 29)
            print(line)
            line = "| "
        print("-" * 29)

if __name__ == '__main__':
    board = Board()
    board.showBoard()
    board.board[1][1] = "O"
    board.showBoard()

I was working on a connect-4 python console demo/game when I got stuck on this really weird issue.

The output of the code above is as follows:

-----------------------------
|   | O |   |   |   |   |   | 
-----------------------------
|   | O |   |   |   |   |   | 
-----------------------------
|   | O |   |   |   |   |   | 
-----------------------------
|   | O |   |   |   |   |   | 
-----------------------------
|   | O |   |   |   |   |   | 
-----------------------------
|   | O |   |   |   |   |   | 
-----------------------------
|   | O |   |   |   |   |   | 
-----------------------------

The weird thing is, I never assigned O to all those positions, I only assigned it to the position [1][1].

I had expected the output to be:

-----------------------------
|   |   |   |   |   |   |   | 
-----------------------------
|   | O |   |   |   |   |   | 
-----------------------------
|   |   |   |   |   |   |   | 
-----------------------------
|   |   |   |   |   |   |   | 
-----------------------------
|   |   |   |   |   |   |   | 
-----------------------------
|   |   |   |   |   |   |   | 
-----------------------------
|   |   |   |   |   |   |   | 
-----------------------------

It is extremely likely that I'm missing something obvious and small but I've been looking and trying for over an hour and I really can't find the problem.

It's not like my board.board list is any more odd than any other two-dimensional list.

[[' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ']]

(It's what I get when I print(board.board))

Copying and pasting that into IDLE I get the following:

>>> a = [[' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ']]
>>> a[1][1] = "O"
[[' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', 'O', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ']]

Which gets me the proper board value.

What is so obviously wrong in my code that I'm missing it? I'm pretty sure that when any of you find an answer that I'll shake my head in shame, it's likely that bad.

Enough self-shaming, so why does my code board.board[1][1] = "O" assign the value "O" to the every single row in board.board?

Changing the first 1 to any other number from 0-6 doesn't change anything either. It's all the same.

回答1:

This is explained in the FAQ, under How do I create a multidimensional list?

The problem is in this part of the code:

board = []
for i in range(7):
    board.append(line)

You're creating a list with 7 references to the same list, line. So, when you modify one, the others all change, because they're the same list.

The solution is to create 7 separate lists, like this:

def createBoard(self):
    board = []
    for i in range(7):
        line = []
        for i in range(7):
            line.append(' ')
        board.append(line)
    return board

Or, more simply, make separate copies of the original list:

def createBoard(self):
    line = []
    for i in range(7):
        line.append(' ')
    board = []
    for i in range(7):
        board.append(line[:])
    return board

While we're at it, you could simplify this tremendously by using list comprehensions:

def createBoard(self):
    return [[' ' for j in range(7)] for i in range(7)]

Or, as the FAQ suggests, you might do better to use a smarter multidimensional array object, like the ones numpy or pandas provide:

def createBoard(self):
    return np.tile(' ', (7, 7))

The disadvantage is that you will need to install numpy, because there's nothing in the standard library that works like this. But the advantage is that you have powerful tools for dealing with arrays—a[1, 1] isn't much simpler than a[1][1], but a[:,1] to access the second column is a lot simpler than [row[1] for row in a].



回答2:

This is an issue of python using references. The 2nd part of CreateBoard

    for i in range(7):
        board.append(line)

Is putting 7 references to the same list in the outer list. So you have a list of 7 copies of the same list. Editing a value in one of these sublists actually updates all of them since they're referencing the same memory.

One option would be to use copy.deepcopy, or to explicitly create a new list for each line

import copy  # at the top of the file
for i in range( 7 ):
    board.append( copy.deepcopy( line ) )

OR

for i in range( 7 ):
    board.append( list(' ' * 7) )