Python如何在循环引用中使用Tkinter变量时避免手动进行内存管理?

时间:2018-07-30 09:04:46

标签: python memory-management tkinter memory-leaks

所以我在Tkinter应用程序上工作,结构某种程度上很复杂,并且子框架和父框架或不同对象之间经常有一些循环引用。

Python 2.7和3.4之前的版本在其中一个对象具有__del__方法时不会收集属于参考周期的对象,而在python 3.4之后,解释器会尽力而为,但在某些情况下,它不会工作(请参见this example

有时会使用Tkinter变量(仅适用于StringVar和IntVar)。 这些类具有__del__方法,因此,当它们是引用循环的一部分时,垃圾收集器不会收集该循环中的任何对象。

这是一个带有pyobjgraph的最小可复制示例,用于显示内存中存在对象(您需要安装Tkinter,pyobjgraph和dot才能运行该对象)。

try :
    import Tkinter as tk
except :
    import tkinter as tk

class ParentWindow(tk.Frame):
    def __init__(self, root):
        self.intvarframes = []
        self.root = root
        self.spawn = tk.Button(root, text="spawn", command=lambda :self.intvarframes.append(FrameWithIntVar(self)))
        self.remove = tk.Button(root, text="remove", command=lambda :self.intvarframes.pop().remove())
        self.spawn.pack()
        self.remove.pack()

    def tryme(self, child):
        print "child"+str(child)

class FrameWithIntVar:
    def __init__(self, parent):
        self.parent = parent
        self.frame = tk.Frame(self.parent.root)
        self.entry = tk.IntVar(self.frame)
        self.entry.trace("w", lambda e : self.parent.tryme(self))
        self.frame.pack()
        self.bigobj = MyVeryBigObject()
        c = tk.Checkbutton(self.frame, text="cb", variable=self.entry)
        c.pack()

    def remove(self):
        self.frame.destroy()
        #del self.entry


class MyVeryBigObject:
    def __init__(self):
        self.values = list(range(10**4))

root = tk.Tk()
ParentWindow(root)
root.mainloop()

import objgraph
if objgraph.by_type("MyVeryBigObject"):
    objgraph.show_backrefs(objgraph.by_type("MyVeryBigObject"), max_depth=10, filename="test.dot")
    from subprocess import check_call
    check_call(['dot', '-Tpng', 'test.dot', '-o', 'test.png'])
else :
    print ("No MyVeryBigObject in memory")

为了演示,只需启动应用程序,生成一些复选框,销毁它们,然后关闭应用程序,然后打开test.png图像。 如您所见,创建复选框的MyVeryBigObject实例数量很多。

在这里发生循环引用,因为lambda self.parent.tryme(self)捕获了self(两次)。

当我在del self.entry方法中取消对remove的注释时。对象已正确释放。

请注意,这是一个简单的示例,在实际应用中,我将必须手动将框架的破坏传播到其所有子代,以确保破坏所有变量。尽管它可以工作,但这将意味着更多的代码,更多的维护以及手动内存管理附带的常见错误。

所以问题是:有没有更简单的方法?

也许当帧被破坏时tkinter会注意到一种方法,或者使用一种没有__del__方法但没有找到任何东西来使用Tkinter变量的方法。

提前谢谢

1 个答案:

答案 0 :(得分:0)

Python 2或Python 3 <3.4

在Tkinter中有一个Destroy事件在子框架被销毁时被调用,因此我只需将其添加到包含Tkinter变量的每个框架中

self.frame.bind("<Destroy>", self.remove)

使用remove方法删除该条目以及当前对象中所有引用该条目的对象。

Python> 3.4

因此在PEP 442之后,解释器似乎不再遇到处理这种特殊情况的困难,但是我没有尝试过,所以如果有人可以确认在Python> 3.4中运行我的示例时没有对象泄漏,那很棒。

请注意,尽管解释程序会尽力释放对象,但在某些情况下它仍然无法工作(请参见this example