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