刷新的 Tkinter 网格可能发生内存泄漏

Possible Memory Leak with Tkinter Grid that Refreshes

我创建了一个内存泄漏问题的 tkinter 程序,最初导致它在大约 40 分钟后锁定。我尝试了一个初始修复,它大大提高了性能,但一段时间后它仍然变慢,所以我认为可能存在第二次内存泄漏 and/or 我想与社区核实的其他问题。

关于程序:后端脚本每六十秒更新一次数据table,然后将其推送到制作数据的 tkinter 脚本table 在一个不错的布局。 (我在下面做了一些示例代码,它不是实际的脚本,它更长)每次刷新时,数据 table 可以有不同数量的 rows/columns。因此,我的 tkinter 脚本需要动态创建 table 并将按钮功能绑定到每个单元格。我刚刚进入 GUI 程序并选择 Tkinter 作为第一个要测试的库。我想使用 GUI 库而不是图表库,因为: 1) 我想学习如何构建基本的 GUI,并认为这将是一个有趣的应用程序; 2) 我希望能够单击我的数据的任何部分 table 并有一个 window 弹出窗口,它将在下次刷新时调整后端脚本的输入。

解决内存泄漏问题和潜在的额外内存泄漏的代码的演变:所以最初,我假设当你在网格位置。版本 1.0 以下:

import pandas as pd
import numpy as np
from tkinter import *

root = Tk()
root.configure(background='black')

#Placeholder for example code

def popupwindow():
    pass

def build():
    mydf = pd.DataFrame([np.arange(1, np.random.randint(3, 7)) * np.random.randint(1,10) for x in np.arange(1, np.random.randint(3, 7))])
    rowindex = 1
    for row in mydf.iterrows():
        colindex = 1
        for i in row[1]:
            label = Label(root, text=str(i), width=7)
            label.bind('<Button-1>', popupwindow)
            label.grid(row=rowindex,column=colindex)
            colindex += 1
        rowindex += 1

    #If grid is smaller than previous grid, remove old widgets
    for label in root.grid_slaves():
        if int(label.grid_info()['row']) > rowindex-1 or int(label.grid_info()['column']) > colindex-1:
            label.grid_forget()

def refresh():
    build()
    #For purpose of example code, I made refresh rate faster than my actual program's 60 seconds  
    root.after(5000, refresh)

refresh()
root.mainloop()

我发现情况并非如此,这是导致第一个内存泄漏问题的原因。所以我创建了 2.0 版,它在重新创建网格之前“忘记”了所有网格从属(请参阅 build() 中的两行新代码)。这大大提高了性能,但我仍然看到 Tkinter 响应超时部分放缓。虽然,它带来了第二个问题,即在 30-40 分钟后,屏幕将部分或完全变黑(我的框架背景是黑色的)并在以下时间后停止刷新:

def build():
    mydf = pd.DataFrame([np.arange(1, np.random.randint(3, 7)) * np.random.randint(1,10) for x in np.arange(1, np.random.randint(3, 7))])

    # I ADDED THESE TWO LINES OF CODE
    for label in root.grid_slaves():
        label.grid_forget()

    rowindex = 1
    for row in mydf.iterrows():
        colindex = 1
        for i in row[1]:
            label = Label(root, text=str(i), width=7)
            label.bind('<Button-1>', popupwindow)
            label.grid(row=rowindex,column=colindex)
            colindex += 1
        rowindex += 1

    # REMOVED THESE 3 LINES OF CODE AS NOW REDUNDANT WITH ADDED CODE ABOVE
    # for label in root.grid_slaves():
    #     if int(label.grid_info()['row']) > rowindex-1 or int(label.grid_info()['column']) > colindex-1:
    #         label.grid_forget()

仔细阅读论坛后,我在 Overstack () 上看到了这个 post,这可能表明小部件标签可能永远不会被垃圾收集,即使被遗忘。不确定这是否是一个准确的解释,但如果是的话,这可能是我的 V2.0 加班减慢的另一个可能原因,因为我总是忘记并且从不重写我的标签。因此,我为 V3 提出的解决方案是使用 if else 函数来查看标签是否已存在于给定位置,如果不创建新标签,如果存在则调整它。我的问题是,这是您的处理方式吗?您可以从我的基本示例中看到另一个内存 leak/performance 问题吗?如果您对我的代码有其他建议的调整,例如我如何动态创建数据 table,请随时提供任何 input/improvements!由于我是编程新手,所以我对不同的想法和更有效的方法持开放态度。

提前感谢您的帮助!

错误不在网格中,而是在您的代码中。每五秒钟,您就会创建一个新数据框,并为该数据框中的每一行创建新标签,并且您永远不会删除它们。相反,您只是将它们堆叠在一起。

调用 grid_forgetgrid_remove 只会将它们从视图中删除,不会删除对象。

您需要在每次调用 refresh 时删除所有旧小部件,或者重新使用现有标签而不是创建新标签。

Therefore, my proposed solution for V3 would be to use an if else function to see if a label already exists at a given position, if it doesn’t create a new label, if it does exist then adjust it. My question is, is this how you would approach it?

是的。那,并销毁不再使用的旧小部件,而不是将它们从网格中删除。