如何解决tkinter内存泄漏?

时间:2017-10-30 11:11:48

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

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

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

当函数update_content_items()循环时,我发现,内存使用量每秒MB增加MB。我可以确认在注释掉lambda表达式后,内存泄漏消失了。 (如在终端中看到的直播'顶部')

似乎我必须使用lambda,否则Button会有一个错误的索引而用户编辑错误的行,当我刚使用self.list_items[i]时,虽然用户点击了正确的。

有没有办法解决这个问题?如何在用正确的索引和摆脱泄漏的情况下点击右键并编辑它?

相应的代码:

    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,并且不能对Label进行配置。

1 个答案:

答案 0 :(得分:0)

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

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.")