Canvas in Tkinter app for Conway's Game of Life updates so slowly This is Conway's Game of Life written in Python and using Tkinter for GUI.

Problems are:


The "Play" button works so slowly. I can see how the canvas is redrawing from top to bottom. But when I quickly click on the "step" button - I see that I want to get from "play" button: I don't see redrawing process. How can I make it faster and proper?
The __init__ method confuses me as it's so large. Are there a ways to reorganize methods or have I done it right?
How can I make this code more pythonic?


gui.py

# -*- coding: utf-8 -*-
from Tkinter import *
from processor import *
from functools import partial
import threading


class LifeGUI:
    def __init__(self, height, width):
        self.height = height
        self.width = width
        self.prev_x = self.prev_y = None
        self.root = Tk()
        self.field = init_field(height=self.height, width=self.width)
        self.canvas = Canvas(self.root, width=10 * len(self.field[0]), height=10 * len(self.field))
        self.clear()
        self.next_button = Button(self.root, bg="blue", text="step", command=self.step)
        self.next_button.pack()
        self.clear_button = Button(self.root, bg="yellow", text="clean", command=self.clear)
        self.clear_button.pack()
        self.clear_button = Button(self.root, bg="green", text="play", command=partial(self.step, {'repeat': True}))
        self.clear_button.pack()
        self.clear_button = Button(self.root, bg="red", text="stop", command=self.stop)
        self.clear_button.pack()
        self.canvas.bind("&lt;Button-1&gt;", self.switch_cell)
        self.canvas.bind("&lt;B1-Motion&gt;", self.switch_cell)
        self.canvas.bind("&lt;ButtonRelease-1&gt;", self.forget)
        self.canvas.pack()
        self.timer = threading.Timer(0.1, partial(self.step, {'repeat': True}))
        self.root.mainloop()

    def switch_cell(self, event):
        x = event.x / 10
        y = event.y / 10
        if (self.prev_x, self.prev_y) != (x, y):
            self.field[y][x] = not self.field[y][x]
            self.redraw()
        self.prev_x, self.prev_y = x, y

    def forget(self, event):
        self.prev_y = self.prev_x = None

    def step(self, repeat=False):
        self.field = next_step(self.field)
        self.redraw()
        if repeat:
            self.timer = threading.Timer(0.1, partial(self.step, {'repeat': True}))
            self.timer.start()

    def stop(self):
        self.timer.cancel()
        self.timer = None

    def clear(self):
        self.field = init_field(height=self.height, width=self.width)
        self.redraw()

    def redraw(self):
        self.canvas.delete("all")
        for i in range(len(self.field)):
            for j in range(len(self.field[0])):
                self.canvas.create_rectangle(j * 10, i * 10, (j + 1) * 10 - 2, (i + 1) * 10 - 2, fill="blue" if self.field[i][j] else "orange")



def main():

    LifeGUI(40, 40)

if __name__ == '__main__':
    main()


processor.py

# -*- coding: utf-8 -*-
def init_field(height=10, width=10, alives=()):
    fld = [[False] * width for _ in range(height)]
    for alive in alives:
        fld[alive[0]][alive[1]] = True
    return fld

def alive_neighbours(field, x, y):
    result = []
    for i in range(-int(y != 0), int(y != len(field[0]) - 1) + 1):
        for j in range(-int(x != 0), int(x != len(field) - 1) + 1):
            if (i or j) and field[x + j][y + i]:
                result.append((x + j, y + i))
    return result

def next_step(fld):
    result = [row[:] for row in fld]
    for i in range(len(fld)):
        for j in range(len(fld[1])):
            if fld[i][j] and len(alive_neighbours(fld, i, j)) not in (2, 3):
                result[i][j] = False
            elif len(alive_neighbours(fld, i, j)) == 3:
                result[i][j] = True
    return result
A few things to make your code better/faster :



You can use tuple unpacking to rewrite :

def init_field(height=10, width=10, alives=()):
    fld = [[False] * width for _ in range(height)]
    for alive in alives:
        fld[alive[0]][alive[1]] = True
    return fld


like this :

def init_field(height=10, width=10, alives=()):
    fld = [[False] * width for _ in range(height)]
    for x, y in alives:
        fld[x][y] = True
    return fld




You could compute alive_neighbours less often by storing the result :

        nb_neighbours = len(alive_neighbours(fld, i, j))
        if fld[i][j] and nb_neighbours not in (2, 3):
            result[i][j] = False
        elif nb_neighbours == 3:
            result[i][j] = True




I must confess that I find alive_neighbours really complicated.