所以我在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变量的方法。
提前谢谢
答案 0 :(得分:0)
在Tkinter中有一个Destroy
事件在子框架被销毁时被调用,因此我只需将其添加到包含Tkinter变量的每个框架中
self.frame.bind("<Destroy>", self.remove)
使用remove
方法删除该条目以及当前对象中所有引用该条目的对象。
因此在PEP 442之后,解释器似乎不再遇到处理这种特殊情况的困难,但是我没有尝试过,所以如果有人可以确认在Python> 3.4中运行我的示例时没有对象泄漏,那很棒。
请注意,尽管解释程序会尽力释放对象,但在某些情况下它仍然无法工作(请参见this example)