Attribute error: Object has no attribute Python

Chubzorz picture Chubzorz · Apr 3, 2014 · Viewed 24.9k times · Source

I am trying to program a version of conway's game of life however I keep getting the message 'Cell' Object Has no attribute 'nextState' when it seems like it should declare the value of nextState before asking to reference it. here is my code:

from tkinter import *
root = Tk()

class Cell (Button):
    Dead = 0
    Live = 1

    def __init__ (self,parent):
        Button.__init__(self,parent, relief = "raised" , width = 2 , borderwidth = 1 , command = self.onpress)
        self.displayState(Cell.Dead)

    def onpress (self):
        if self.state == Cell.Live:
            self.displayState(Cell.Dead)
        elif self.state == Cell.Dead:
            self.displayState(Cell.Live)

    def setNextState (self , Neighbours):
        if self.state == Cell.Live and (Neighbours < 2 or Neighbours > 3):
            self.nextState = Cell.Dead
        elif self.state == Cell.Dead and Neighbours == 3:
            self.nextState = Cell.Live
        elif self.state == Cell.Dead and Neighbours != 3:
            self.nextState = self.state

    def stepToNextState(self):
        self.displayState(self.nextState)

    def displayState (self , newstate):
        self.state = newstate
        if self.state == Cell.Live:
            self["bg"] = "black"
        if self.state == Cell.Dead:
            self["bg"] = "white"

class Grid:
    def __init__(self,parent,sizex,sizey):
        self.sizex = sizex
        self.sizey = sizey
        self.cells = []
        for a in range (0,self.sizex):
            rowcells = []
            for b in range (0, self.sizey):
                c = Cell(parent)
                c.grid(row=b , column=a)
                rowcells.append(c)
            self.cells.append(rowcells)

    def step (self):
        cells = self.cells
        for x in range (0,self.sizex):
            if x==0: x_down = self.sizex-1
            else: x_down = x-1
            if x==self.sizex-1: x_up = 0
            else: x_up = x+1
            for y in range(0,self.sizey):
                if y==0: y_down = self.sizey-1
                else: Y_down = y-1
                if y==self.sizey-1: y_up = 0
                else: y_up = y+1
                sum = cells[x_down][y].state + cells[x_up][y].state + cells[x][y_down].state + cells[x][y_up].state + cells[x_down][y_down].state +cells[x_up][y_up].state + cells[x_down][y_up].state + cells[x_up][y_down].state
                cells[x][y].setNextState(sum)
            for row in cells:
                for cell in row:
                    cell.stepToNextState()

    def clear(self):
        for row in self.cells:
            for cell in row:
                cell.displayState(Cell.Dead)

if __name__ == "__main__":
    frame = Frame(root)
    frame.pack()
    grid = Grid(frame,25,25)
    bottomFrame = Frame(root)
    bottomFrame.pack (side = BOTTOM)
    buttonStep = Button(bottomFrame , text="Step" , command=grid.step)
    buttonStep.pack(side = LEFT)
    buttonClear = Button(bottomFrame, text = "Clear", command=grid.clear)
    buttonClear.pack(side=LEFT , after=buttonStep)
    root.mainloop()

The error message is:

Exception in Tkinter callback
Traceback (most recent call last):
  File "C:\Python33\lib\tkinter\__init__.py", line 1442, in __call__
    return self.func(*args)
  File "C:\Users\Owner\Desktop\Other\Programs & Misc\Python\Tyler's Game of Life.py", line 65, in step
    cell.stepToNextState()
  File "C:\Users\Owner\Desktop\Other\Programs & Misc\Python\Tyler's Game of Life.py", line 27, in stepToNextState
    self.displayState(self.nextState)
AttributeError: 'Cell' object has no attribute 'nextState'

If anyone could point out where the error is occuring / what is causing it and possibly a way to fix it I would be very grateful.

Answer

Blckknght picture Blckknght · Apr 3, 2014

The issue appears to be that your for row in cells loop is inside of your previous for x in range(0, self.sizex) loop. Here's what the step method should look like if you get it correctly indented:

def step (self):
    cells = self.cells
    for x in range (0,self.sizex):
        if x==0: x_down = self.sizex-1
        else: x_down = x-1
        if x==self.sizex-1: x_up = 0
        else: x_up = x+1
        for y in range(0,self.sizey):
            if y==0: y_down = self.sizey-1
            else: Y_down = y-1
            if y==self.sizey-1: y_up = 0
            else: y_up = y+1
            sum = cells[x_down][y].state + cells[x_up][y].state + cells[x][y_down].state + cells[x][y_up].state + cells[x_down][y_down].state +cells[x_up][y_up].state + cells[x_down][y_up].state + cells[x_up][y_down].state
            cells[x][y].setNextState(sum)
    for row in cells:                     # unindent these
        for cell in row:                  # lines by one
            cell.stepToNextState()        # level each

If all the indentation issues are taken care of (or were not in the original code), there's still an issue that may cause an issue in certain situations. The issue is that the Cell.setNextState method doesn't handle every situation. Specifically, it doesn't set nextState if a cell is alive and should stay so (it has two or three living neighbors). The lack of an else on your chain of if and elif statements should have raised this as a red flag for me, but I overlooked it the first time I examined the function.

Here's how it can be fixed:

def setNextState (self , Neighbours):
    if self.state == Cell.Live and (Neighbours < 2 or Neighbours > 3):
        self.nextState = Cell.Dead
    elif self.state == Cell.Dead and Neighbours == 3:
        self.nextState = Cell.Live
    else: # remove the conditions on this block, all the state changes were handled above
        self.nextState = self.state