Search code examples
pythonimmutabilitymutable

Why is this variable changing?


I'm writing a chess interface for fun with my friend, the eventuality being we both make bots and fight them out. I've met a roadblock when I re-evaluate the moves that are legal to look for checks as they should be illegal moves. The actual code for the interface is a couple thousand lines long, but I've simplified the code to show the problem. I'll post the simplified code in which I can replicate the problem and then the key parts which I think cause the problem. The simplified code:

class Pieces():

    def __init__(self):

        self.white_pawns_inf = [[0, 1, False, True], [1, 1, False, True], [2, 1, False, True], [3, 1, False, True], [4, 1, False, True], [5, 1, False, True], [6, 1, False, True], [7, 1, False, True]]
        self.white_bishops_inf = [[2, 0, False], [5, 0, False], [0, 7, False], [0, 7, False], [0, 7, False], [0, 7, False], [0, 7, False], [0, 7, False], [0, 7, False], [0, 7, False]]
        self.white_knights_inf = [[1, 0, False], [6, 0, False], [0, 7, False], [0, 7, False], [0, 7, False], [0, 7, False], [0, 7, False], [0, 7, False], [0, 7, False], [0, 7, False]]
        self.white_rooks_inf = [[0, 0, False, True], [7, 0, False, True], [0, 7, False, False], [0, 7, False, False], [0, 7, False, False], [0, 7, False, False], [0, 7, False, False], [0, 7, False, False], [0, 7, False, False], [0, 7, False, False]]
        self.white_queens_inf = [[3, 0, False], [0, 7, False], [0, 7, False], [0, 7, False], [0, 7, False], [0, 7, False], [0, 7, False], [0, 7, False], [0, 7, False]]
        self.white_king_inf = [[4, 0, True, True]]

        self.black_pawns_inf = [[0, 6, True, True], [1, 6, True, True], [2, 6, True, True], [3, 6, True, True], [4, 6, True, True], [5, 6, True, True], [6, 6, True, True], [7, 6, True, True]]
        self.black_bishops_inf = [[2, 2, True], [5, 7, True], [0, 7, False], [0, 7, False], [0, 7, False], [0, 7, False], [0, 7, False], [0, 7, False], [0, 7, False], [0, 7, False]]
        self.black_knights_inf = [[6, 2, True], [6, 7, True], [0, 7, False], [0, 7, False], [0, 7, False], [0, 7, False], [0, 7, False], [0, 7, False], [0, 7, False], [0, 7, False]]
        self.black_rooks_inf = [[0, 7, True, True], [7, 7, True, True], [0, 7, False, False], [0, 7, False, False], [0, 7, False, False], [0, 7, False, False], [0, 7, False, False], [0, 7, False, False], [0, 7, False, False], [0, 7, False, False]]
        self.black_queens_inf = [[3, 7, True], [0, 7, False], [0, 7, False], [0, 7, False], [0, 7, False], [0, 7, False], [0, 7, False], [0, 7, False], [0, 7, False]]
        self.black_king_inf = [[4, 7, True, True]]

        self.white_occupation_x = [0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7]
        self.white_occupation_y = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1]

        self.black_occupation_x = [0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7]
        self.black_occupation_y = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1]

    def check_checks(self):

        take = True

        for notation_val in ['a6', 'a5', 'b6', 'b5', 'c6', 'c5', 'd6', 'd5', 'e6', 'e5', 'f6', 'f5', 'g6', 'g5', 'h6', 'h5']:

            #print(self.black_pawns_inf)
            black_pawns = self.black_pawns_inf
            #print(self.black_pawns_inf)

            if True:

                if True:
                        
                    tox = notation.get_column_char(notation_val[-2])
                    toy = int(notation_val[-1]) - 1

                    if True:

                        fromx = tox

                        if True:

                            if toy == 4:

                                fromy = toy + 2

                                for i in range(0, 8):

                                    if black_pawns[i][2] == True and black_pawns[i][0] == fromx and black_pawns[i][1] == toy + 1:

                                        fromy = toy + 1

                            else:

                                fromy = toy + 1

                    if True:

                        for i in range(0, 8):

                            if black_pawns[i][2] == True and black_pawns[i][0] == fromx and black_pawns[i][1] == fromy:

                                print(self.black_pawns_inf)
                                
                                black_pawns[i][0] = tox
                                black_pawns[i][1] = toy
                                black_pawns[i][3] = False
                                
                                print(self.black_pawns_inf)
                                
class Notation():

    def __init__(self):

        pass
    
    def get_column(self, x):
        
        if x == 0:

            return "a"

        elif x == 1:

            return "b"

        elif x == 2:

            return "c"

        elif x == 3:

            return "d"

        elif x == 4:

            return "e"

        elif x == 5:

            return "f"

        elif x == 6:

            return "g"

        elif x == 7:

            return "h"

    def get_column_char(self, x):
        
        if x == "a":

            return 0

        elif x == "b":

            return 1

        elif x == "c":

            return 2

        elif x == "d":

            return 3

        elif x == "e":

            return 4

        elif x == "f":

            return 5

        elif x == "g":

            return 6

        elif x == "h":

            return 7

    def get_row(self, y):

        for i in range(0, 8):

            if y == i:

                return str(i + 1)

        if y != 0 and y != 1 and y != 2 and y != 3 and y != 4 and y != 5 and y != 6 and y != 7:

            return "9"

white_turn = False
        
pieces = Pieces()
notation = Notation()
#print(pieces.black_pawns_inf)
pieces.check_checks()
#print(pieces.black_pawns_inf)

And the key parts are:

print(self.black_pawns_inf)

black_pawns[i][0] = tox
black_pawns[i][1] = toy
black_pawns[i][3] = False

print(self.black_pawns_inf)

And

black_pawns = self.black_pawns_inf

The issue is, that the variable "self.black_pawns_inf" changes in the function, where it occurs once (excluding print statements) in which black_pawns is set to it. It makes no sense to me that this variable changes. The isolated area of code where the variable changes is in the first key part as I have shown above. I print the variable before and after, and it changes, even though the 3 lines of code that supposedly change it do not even include this variable!!! I assume I am missing something in python that it creates a link between variables that are set to each other and somehow by changing one, it also changes the other. Below I will show the output when the program is run for the first iteration of "for notation_val in ['a6', 'a5', 'b6', 'b5', 'c6', 'c5', 'd6', 'd5', 'e6', 'e5', 'f6', 'f5', 'g6', 'g5', 'h6', 'h5']:" in which the change occurs:

[[0, 6, True, True], [1, 6, True, True], [2, 6, True, True], [3, 6, True, True], [4, 6, True, True], [5, 6, True, True], [6, 6, True, True], [7, 6, True, True]]
[[0, 5, True, False], [1, 6, True, True], [2, 6, True, True], [3, 6, True, True], [4, 6, True, True], [5, 6, True, True], [6, 6, True, True], [7, 6, True, True]]

as you can see, the first list in each list changes - 6 becomes 5 and True becomes False - this is expected for the variable "black_pawns", but also happens for the variable "self.black_pawns_inf"...

I've tried to explain the problem as best I can, any help would be appreciated, thanks!


Solution

  • I assume I am missing something in python that it creates a link between variables that are set to each other and somehow by changing one, it also changes the other.

    You're right - some types in Python such as lists are mutable and will display this behaviour. Other types such as ints and strs are immutable and do not have this behaviour. See a previous answer on this topic for an explanation.

    You could make a copy of self.black_pawns_inf to avoid modifying it in place. In your case, your have a list of lists, so you will need to make a deep copy to avoid modifying it in place:

    from copy import deepcopy
    
    black_pawns = deepcopy(self.black_pawns_inf)
    

    You will then be able to modify the elements of black_pawns without modifying those of self.black_pawns_inf.