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