Tk Python - .trace留下对object的引用,并且不允许该对象被垃圾收集



我的基本问题是这样的,我有一个对象被创建和销毁(即垃圾收集),直到我引入下面的行:

self.variableA.trace("w", self.printSomeStuff)

variableA是一个stringVar,其设置如下所示

self.variableA = tk.StringVar(self.detailViewHolderFrame)
self.variableA.set(self.OPTIONS0[0])

由于某些原因,这一行导致存在对对象的引用,因此它永远不会被垃圾收集,最终导致内存泄漏。

除了使用不同的小部件外,还有谁能告诉我如何解决这个问题呢?

我可以进一步扩展代码,但首先我想看看这是否是一个常见的问题,如果有人知道潜在的问题

您可以尝试使用trace_vdelete()删除回调。希望这将删除对tk变量的引用。

def cb(*args):
    print args
v = StringVar()
v.trace('r', cb)
v.trace('w', cb)
v.set(10)   # cb called
# ('PY_VAR5', '', 'w')
v.get()     # cb called
# ('PY_VAR5', '', 'r')
# '10'
print v.trace_vinfo()
# [('w', '139762300731216cb'), ('r', '139762505534512cb')]
for ti in v.trace_vinfo():
    print "Deleting", ti
    v.trace_vdelete(*ti)
# Deleting ('w', '139762300731216cb')
# Deleting ('r', '139762505534512cb')
print v.trace_vinfo()
#

您需要弄清楚如何调用trace_vdelete(),可能您可以通过子类化和重写__del__()来从tk变量的__del__()方法中做到这一点:

class MyStringVar(StringVar):
    def __del__(self):
        for t in self.trace_vinfo():
            self.trace_vdelete(*t)
        StringVar.__del__(self)

正如blackknight所指出的,__del__直到对象被垃圾收集之前才会被调用,而这在跟踪StringVar时不会发生,因为有一个额外的引用被保存。

一个解决方案是使用外部函数作为跟踪回调。

如果,然而,你希望跟踪回调是你的类的一个方法,一个解决方案是将你的类设置为一个上下文管理器,即一个可以在with语句中使用的类。然后,可以在with语句结束时调用清理方法。清理方法将删除跟踪回调,这将从tk跟踪系统中删除对对象的引用。然后,它应该可用于垃圾收集。下面是一个例子:

import Tkinter as tk
root = tk.Tk()
class YourClass(object):
    def __init__(self):
        self.s = tk.StringVar()
        self.s.trace('w',self.cb)
        self.s.trace('r',self.cb)
    def __enter__(self):
        """Make this class usable in a with statement"""
        return self
    def __exit__(self, exc_type, exc_value, traceback):
        """Make this class usable in a with statement"""
        self.cleanup()
    def __del__(self):
        print 'YourClass.__del__():'
    def cb(self, *args):
        print 'YourClass.cb(): {}'.format(args)
    def cleanup(self):
        print 'YourClass.cleanup():'
        for t in self.s.trace_vinfo():
            print 'YourClass.cleanup(): deleting {}'.format(t)
            self.s.trace_vdelete(*t)

>>> obj = YourClass()
>>> obj.s.set('hi')
YourClass.cb(): ('PY_VAR5', '', 'w')
>>> obj.s.get()
YourClass.cb(): ('PY_VAR5', '', 'r')
'hi'
>>> obj.s.trace_vinfo()
[('r', '139833534048848cb'), ('w', '139833534047728cb')]
>>> del obj
>>> 

注意:YourClass.__del__()没有被调用,因为跟踪系统仍然持有对YourClass实例的引用。您可以手动调用cleanup()方法:

>>> obj = YourClass()
>>> obj.cleanup()
YourClass.cleanup():
YourClass.cleanup(): deleting ('r', '139833533984192cb')
YourClass.cleanup(): deleting ('w', '139833533983552cb')
>>> del obj
YourClass.__del__():

调用cleanup()将删除对YourClass实例的引用,并且可以进行垃圾收集。每次都很容易忘记调用cleanup(),这也是一种痛苦。使用上下文管理器使调用清理代码变得容易:

>>> with YourClass() as obj:
...     obj.s.set('hi')
...     obj.s.get()
...     obj.s.trace_vinfo()
... 
YourClass.cb(): ('PY_VAR2', '', 'w')
YourClass.cb(): ('PY_VAR2', '', 'r')
'hi'
[('r', '139833534001392cb'), ('w', '139833533984112cb')]
YourClass.cleanup():
YourClass.cleanup(): deleting ('r', '139833534001392cb')
YourClass.cleanup(): deleting ('w', '139833533984112cb')

这里__exit__()在退出with语句时被自动调用,并委托给cleanup()。删除或重新绑定obj将导致垃圾收集,因为变量将超出作用域,例如在从函数返回时:

>>> obj = None
YourClass.__del__():

使用上下文管理器的最后一个好处是,无论出于何种原因,在退出上下文管理器时总是会调用__exit()__。这包括任何未处理的异常,因此您的清理代码将始终被调用:

>>> with YourClass() as obj:
...     1/0
... 
YourClass.cleanup():
YourClass.cleanup(): deleting ('r', '139833395464704cb')
YourClass.cleanup(): deleting ('w', '139833668711040cb')
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
ZeroDivisionError: integer division or modulo by zero
>>> del obj
YourClass.__del__():

最新更新