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

Tk Python - .trace leaves reference to object and doesnt allow the object to be garbage collected

我的问题的基本原理是这样的,我有一个对象可以很好地创建和销毁(即垃圾收集),直到我介绍以下行:

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(),也许您可​​以从 tk 变量的 __del__() 方法通过子classing 和覆盖 [=20] 来做到这一点=]:

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

更新

一样,__del__ 直到对象被垃圾回收之前才会被调用,并且在跟踪 StringVar 时不会发生这种情况,因为附加引用是举办。

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

但是,如果您希望跟踪回调成为 class 的一种方法,解决方案是将 class 设置为上下文管理器,即 class可以在 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
>>> 

N.B。 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 将导致垃圾收集,变量超出范围也会导致垃圾收集,例如 return 来自函数:

>>> 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__():