【问题标题】:How to solve a tkinter memory leak?如何解决 tkinter 内存泄漏?
【发布时间】:2018-04-11 08:22:41
【问题描述】:

我有一个具有固定行号的动态表(如 FIFO 队列),它通过 tkinter 的 after() 函数不断更新。表格内部是一个按钮,其文本是可编辑的。

为了使 Button 的文本可编辑,我使用了 BrenBarn 的 solution,并将循环变量引用到 command-attribute 处的函数调用中。

当函数update_content_items()被循环时,我发现内存使用量每秒增加MB。我可以确认,在注释掉 lambda 表达式后,内存泄漏就消失了。 (如终端中实时运行的“顶部”所示)

看来我必须使用 lambda,否则当我刚刚使用 self.list_items[i] 时,Button 会有错误的索引并且用户编辑了错误的行,尽管用户单击了正确的行。

有没有办法解决这个问题?用户如何在拥有正确索引并摆脱泄漏的同时单击右侧按钮并对其进行编辑?

对应代码:

    def update_content_items(self):
    """
    Continuously fills and updates the Table with rows and content.
    The size of the table rows is initially fixed by an external value at config.ini
    :return: nothing
    """
    if len(self.list_items) > self.queueMaxlen:
        self.queueMaxlen = len(self.list_items)
        self.build_table()

    try:
        for i in range(len(self.list_items)):
            item = self.list_items[i]
            self.barcodeImgList[i].image = item.plateimage
            orig_image = Image.open(io.BytesIO(item.plateimage))
            ein_image = ImageTk.PhotoImage(orig_image)
            self.barcodeImgList[i].configure(image=ein_image)
            # keeps a reference, because somehow tkinter forgets it...??? Bug of my implementation???
            self.barcodeImgList[i].image = ein_image
            orig_image = None
            ein_image = None
            #FIXME Memory LEAK?
            self.numberList[i].configure(text=item.number,
                                         command=lambda K=i: self.edit_barcode(self.list_items[K]))
            self.timestampList[i].configure(text=item.timestamp)
            self.search_hitlist[i].config(bg='white', cursor="xterm")
            self.search_hitlist[i].unbind("<Button-1>")

            if item.queryresult is not None:
                if item.queryresult.gesamtstatus != 'Gruen':
                    self.search_hitlist[i].insert(tk.END, item.queryresult.barcode +
                                                  '\n' + item.queryresult.permitlevel)
                    self.search_hitlist[i].configure(bg='red', cursor="hand2")
                    self.search_hitlist[i].bind("<Button-1>", item.url_callback)
                else:
                    self.search_hitlist[i].configure(bg='green', cursor="xterm")
            self.search_hitlist[i].configure(state=tk.DISABLED)
        self.on_frame_configure(None)
        self.canvas.after(10, self.update_content_items)
    except IndexError as ie:
        for number, thing in enumerate(self.list_items):
            print(number, thing)
        raise ie

def edit_barcode(self, item=None):
    """
    Opens the number plate edit dialogue and updates the corresponding list item.
    :param item: as Hit DAO
    :return: nothing
    """
    if item is not None:
        new_item_number = EditBarcodeEntry(self.master.master, item)
        if new_item_number.mynumber != 0:
            item.number = new_item_number.mynumber
            self.list_items.request_work(item, 'update')
            self.list_items.edit_hititem_by_id(item)
            self.parent.master.queryQueue.put(item)
    else:
        print("You shouldn't get here at all. Please see edit_barcode function.")

编辑:似乎确实存在更深层次的内存泄漏(python 本身)。图像不会被垃圾收集。 Memory is slowly leaking in Python 3.x 我确实使用 PIL。也在这里:Image loading by file name memory leak is not properly fixed

我该怎么办,因为我必须循环浏览包含记录的列表并更新带有图像的标签?有解决方法吗? PhotoImage 没有明确的 close() 函数,如果我调用 del,则引用是 gc'ed 并且无法配置标签。

【问题讨论】:

  • 您正在使用越来越多的内存,因为您不断地创建必须保存在内存中的匿名函数(这就是 lambda 所做的)。解决这个问题的唯一方法是使用绑定而不是命令回调,然后在您的edit_barcode 中找出小部件在列表中的索引(使用 event.widget),然后使用该索引获取项目跨度>
  • 作为您对 tkinter “忘记”您的图像的评论,这不是错误,这是 python 的一个功能,垃圾收集器将从内存中删除不再引用的项目,这就是您拥有的原因将其分配给小部件的属性,以保持对它的引用。

标签: button lambda tkinter memory-leaks python-3.5


【解决方案1】:

我提出的更改示例,缩进已修复:

def update_content_items(self):
    """
    Continuously fills and updates the Table with rows and content.
    The size of the table rows is initially fixed by an external value at config.ini
    :return: nothing
    """
    if len(self.list_items) > self.queueMaxlen:
        self.queueMaxlen = len(self.list_items)
        self.build_table()

    try:
        for i in range(len(self.list_items)):
            item = self.list_items[i]
            self.barcodeImgList[i].image = item.plateimage
            orig_image = Image.open(io.BytesIO(item.plateimage))
            ein_image = ImageTk.PhotoImage(orig_image)
            self.barcodeImgList[i].configure(image=ein_image)
            # keeps a reference, because somehow tkinter forgets it...??? Bug of my implementation???
            self.barcodeImgList[i].image = ein_image
            orig_image = None
            ein_image = None

            self.numberList[i].configure(text=item.number) # removed lambda
            self.numberList[i].bind("<Button-1>", self.edit_barcode_binding) # added binding
            self.timestampList[i].configure(text=item.timestamp)
            self.search_hitlist[i].config(bg='white', cursor="xterm")
            self.search_hitlist[i].unbind("<Button-1>")

            if item.queryresult is not None:
                if item.queryresult.gesamtstatus != 'Gruen':
                    self.search_hitlist[i].insert(tk.END, item.queryresult.barcode +
                                                  '\n' + item.queryresult.permitlevel)
                    self.search_hitlist[i].configure(bg='red', cursor="hand2")
                    self.search_hitlist[i].bind("<Button-1>", item.url_callback)
                else:
                    self.search_hitlist[i].configure(bg='green', cursor="xterm")
            self.search_hitlist[i].configure(state=tk.DISABLED)
        self.on_frame_configure(None)
        self.canvas.after(10, self.update_content_items)
    except IndexError as ie:
        for number, thing in enumerate(self.list_items):
            print(number, thing)
        raise ie

def edit_barcode_binding(self, event): # new wrapper for binding
    K = self.numberList.index(event.widget) # get index from list
    self.edit_barcode(self.list_items[K]) # call the original function

def edit_barcode(self, item=None):
    """
    Opens the number plate edit dialogue and updates the corresponding list item.
    :param item: as Hit DAO
    :return: nothing
    """
    if item is not None:
        new_item_number = EditBarcodeEntry(self.master.master, item)
        if new_item_number.mynumber != 0:
            item.number = new_item_number.mynumber
            self.list_items.request_work(item, 'update')
            self.list_items.edit_hititem_by_id(item)
            self.parent.master.queryQueue.put(item)
    else:
        print("You shouldn't get here at all. Please see edit_barcode function.")

【讨论】:

  • 谢谢詹姆斯。您的更改有效,但内存泄漏仍然存在。我以调试模式启动应用程序并使用“import objgraph”并调用:“objgraph.show_most_common_types(limit=25)”。在这里,我看到方法类型增长得非常快,尽管应用程序处于闲置状态。
猜你喜欢
  • 2015-06-28
  • 1970-01-01
  • 1970-01-01
  • 2021-11-18
  • 2020-04-07
  • 1970-01-01
  • 2013-09-02
相关资源
最近更新 更多