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

2024-06-28 19:29:04 发布

您现在位置:Python中文网/ 问答频道 /正文

所以我正在开发一个Tkinter应用程序,它的结构有点复杂,而且子帧和父帧或不同对象之间经常有一些循环引用。你知道吗

Python2.7和3.4之前的版本不收集作为引用循环一部分的对象,当其中一个具有__del__方法时,在Python3.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")

为了演示,只需启动应用程序,生成一些复选框,销毁它们并关闭应用程序,然后打开测试.png形象。 如您所见,MyVeryBigObject的实例与您创建的复选框一样多。你知道吗

这里发生循环引用是因为lambda self.parent.tryme(self)捕获self(两次)。你知道吗

当我取消对remove方法中的del self.entry的注释时。对象被正确释放。你知道吗

请注意,这是一个简单的示例,在实际应用程序中,我必须手动将对帧的销毁传播到其所有子帧,以确保销毁所有变量。虽然它可以工作,但它意味着更多的代码,更多的维护,以及可能的常见错误,这些都是手动内存管理带来的。你知道吗

所以问题是:有没有更简单的方法?你知道吗

也许有一种方法可以让tkinter在一个帧被破坏时注意到,或者有一种方法可以在没有__del__方法的情况下使用tkinter变量,但是我还没有找到任何东西。你知道吗

先谢谢你


Tags: 对象方法self应用程序tkinterdefrootframe
1条回答
网友
1楼 · 发布于 2024-06-28 19:29:04

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

相关问题 更多 >