Skip to main content
 首页 » 编程设计

python之Canvas 更新变慢(可能需要释放一些对象)

2025年05月04日21del

这个游戏在大约 10 次迭代后变慢了。我很确定这是由于垃圾对象(可能是我在每次迭代中创建的矩形)位于那里并占用空间,即使它们不需要。如何识别不需要的对象并将其删除?

# Game of life 
from random import randint 
import numpy as np 
from copy import deepcopy 
from enum import Enum 
import tkinter as tk 
 
 
class State(Enum): 
    Dead    = 0 
    Alive   = 1 
 
    def __str__(self): 
        return str(self.value) 
 
 
class Cell:     
    def __init__(self, m, n, state): 
        self.m = np.uint(m) 
        self.n = np.uint(n) 
        self.state = state 
 
    def kill(self): 
        self.state = State.Dead 
 
    def birth(self): 
        self.state = State.Alive 
 
    def __str__(self): 
        return '({},{}) {}'.format(self.m, self.n, self.state) 
 
    def __repr__(self): 
        return '({},{}) {}'.format(self.m, self.n, self.state) 
 
 
class Game: 
    def __init__(self, m, n, alive_cells = None): 
        self.m = m 
        self.n = n 
        self.grid = np.ndarray((m,n), dtype = np.uint8) 
 
        if alive_cells: 
            self.cells = [Cell(i // n,i % n, State.Alive if (i // n,i % n) in alive_cells else State.Dead) for i in range(m*n)] 
        else: 
            self.cells = [Cell(i / n,i % n,randint(0,1)) for i in range(m*n)] 
 
        # GUI # 
        self.top = tk.Tk() 
        self.cell_size = 10000 // 400 #(self.m * self.n) 
        self.canvas = tk.Canvas(self.top, bg="gray", height=self.m *self. cell_size, width=self.n * self.cell_size)   
 
    def populate_grid(self): 
        for cell in self.cells: 
            self.grid[cell.m,cell.n] = cell.state.value 
 
    def show(self, show_GUI = True, print_2_console = False): 
        self.populate_grid() 
 
        if print_2_console: 
            print('#'*self.m*3) 
            print(self.grid) 
        if show_GUI: 
            self.draw_canvas() 
 
    def iterate(self): 
        ''' 
        Rules: 
        (1) If cell has less than 2 neighbours, it dies 
        (2) If cell has more than 3 neighbours, it dies 
        (3) If cell has 2-3 neighbours, it survives 
        (4) If cell has 3 neighbours, it rebirths 
        ''' 
        new_cells = [] 
        for cell in self.cells: 
            alive_neighbours = 0 
            for i in range(cell.m - 1, cell.m + 2): 
                for j in range(cell.n - 1, cell.n + 2): 
                    if i == cell.m and j == cell.n: 
                        continue 
                    else: 
                        try: 
                            alive_neighbours += self.grid[i,j] 
                        except IndexError: 
                            pass 
 
 
            tmp = deepcopy(cell) 
 
            if alive_neighbours < 2 or alive_neighbours > 3: 
                tmp.kill() 
            elif alive_neighbours == 3: 
                tmp.birth() 
            else: # == 2 
                pass  
 
            new_cells.append(tmp) 
 
        self.cells = new_cells 
        self.show() 
 
    def draw_canvas(self):     
        for cell in self.cells: 
            if cell.state == State.Alive: 
                color = 'blue' 
            else: 
                color = 'red' 
 
            self.canvas.create_rectangle(cell.n*self.cell_size, cell.m*self.cell_size, (1+cell.n)*self.cell_size, (1+cell.m)*self.cell_size, fill=color) 
 
        self.canvas.pack() 
        self.update_canvas() 
        self.top.mainloop() 
 
    def update_canvas(self):  
        for cell in self.cells: 
            if cell.state == State.Alive: 
                color = 'blue' 
            else: 
                color = 'red' 
 
            self.canvas.create_rectangle(cell.n*self.cell_size, cell.m*self.cell_size, (1+cell.n)*self.cell_size, (1+cell.m)*self.cell_size, fill=color) 
 
        # call again after 100 ms 
        self.top.after(100, self.iterate) 
 
 
 
if __name__ == "__main__": 
    glider = (20, 20, ((1,3), (2,3), (2,1), (3,2), (3,3))) 
    small_exploder = (30, 30, ((10,10), (11,9), (11,10), (11,11), (12,9), (12,11), (13,10))) 
 
 
    M, N, STARTING_LIVE_CELLS, ITERATIONS = *small_exploder, 0 
 
    g = Game(M, N, STARTING_LIVE_CELLS) 
    g.show() 

请您参考如下方法:

我终于弄明白了,您的脚本可以正常运行而不会崩溃。解决垃圾对象问题后我遇到的递归问题是因为 mainloop() 被一遍又一遍地调用。它只应在 tkinter 脚本中调用一次。

此版本通过使用 相同 标记值“标记”它们来避免不断创建越来越多的 tkinter 矩形,这允许使用单个 Canvas 将它们作为一个组删除.delete() 方法调用。

每次迭代仅修改现有矩形会更有效,但此版本每次迭代仅删除并重新创建它们。要更新它们,需要通过某种方式在创建后单独引用它们。如果我有动力并且您有兴趣,我可能会稍后更新此答案。

我做了很多更改,但您的核心代码仍然存在。

# Game of life 
from random import randint 
import numpy as np 
from copy import deepcopy 
from enum import Enum 
import tkinter as tk 
 
 
class State(Enum): 
    Dead = 0 
    Alive = 1 
 
    def __str__(self): 
        return str(self.value) 
 
    @property 
    def color(self): 
        return 'blue' if self.value else 'red' 
 
 
class Cell: 
    def __init__(self, m, n, state): 
        self.m = np.uint(m) 
        self.n = np.uint(n) 
        self.state = state 
 
    def kill(self): 
        self.state = State.Dead 
 
    def birth(self): 
        self.state = State.Alive 
 
    def __str__(self): 
        return '({},{}) {}'.format(self.m, self.n, self.state) 
 
    def __repr__(self): 
        return '({},{}) {}'.format(self.m, self.n, self.state) 
 
 
class Game: 
    CELL_TAG = 'cells' 
 
    def __init__(self, m, n, alive_cells = None): 
        self.m = m 
        self.n = n 
        self.grid = np.ndarray((m,n), dtype = np.uint8) 
 
        if alive_cells: 
            self.cells = [Cell(i // n, i % n, 
                State.Alive if (i // n, i % n) in alive_cells else State.Dead) 
                    for i in range(m*n)] 
        else: 
            self.cells = [Cell(i / n, i % n, randint(0,1)) for i in range(m*n)] 
 
        # GUI # 
        self.top = tk.Tk() 
        self.cell_size = 10000 // 400  # (self.m * self.n) 
        self.canvas = tk.Canvas(self.top, bg="gray", height=self.m * self.cell_size, 
                                                      width=self.n * self.cell_size) 
        self.canvas.pack() 
 
    def populate_grid(self): 
        for cell in self.cells: 
            self.grid[cell.m, cell.n] = cell.state.value 
 
    def show(self, show_GUI = True, print_2_console = False): 
        self.populate_grid() 
 
        if print_2_console: 
            print('#'*self.m*3) 
            print(self.grid) 
        if show_GUI: 
            self.draw_canvas() 
            self.top.mainloop() 
 
    def iterate(self): 
        ''' 
        Rules: 
        (1) If cell has less than 2 neighbours, it dies 
        (2) If cell has more than 3 neighbours, it dies 
        (3) If cell has 2-3 neighbours, it survives 
        (4) If cell has 3 neighbours, it rebirths 
        ''' 
        new_cells = [] 
        for cell in self.cells: 
            alive_neighbours = 0 
            for i in range(cell.m - 1, cell.m + 2): 
                for j in range(cell.n - 1, cell.n + 2): 
                    if i == cell.m and j == cell.n: 
                        continue 
                    else: 
                        try: 
                            alive_neighbours += self.grid[i,j] 
                        except IndexError: 
                            pass 
 
            tmp = deepcopy(cell) 
 
            if alive_neighbours < 2 or alive_neighbours > 3: 
                tmp.kill() 
            elif alive_neighbours == 3: 
                tmp.birth() 
            else: # == 2 
                pass 
 
            new_cells.append(tmp) 
 
        self.cells = new_cells 
        self.populate_grid() 
        self.draw_canvas() 
 
    def draw_canvas(self): 
        self.canvas.delete(self.CELL_TAG)  # Gets rid of any existing cell rects. 
 
        for cell in self.cells: 
            self.canvas.create_rectangle( 
                cell.n*self.cell_size, cell.m*self.cell_size, 
                (1+cell.n)*self.cell_size, (1+cell.m)*self.cell_size, 
                fill=cell.state.color, tag=self.CELL_TAG) 
 
        self.top.after(100, self.iterate) 
 
 
if __name__ == "__main__": 
    glider = (20, 20, ((1,3), (2,3), (2,1), (3,2), (3,3))) 
    small_exploder = (30, 30, ((10,10), (11,9), (11,10), (11,11), (12,9), (12,11), (13,10))) 
 
    M, N, STARTING_LIVE_CELLS, ITERATIONS = *small_exploder, 0 
 
    g = Game(M, N, STARTING_LIVE_CELLS) 
    g.show()