the basics of my problem is this, I have an object which gets created and destroyed fine (i.e. garbage collected) until I introduce the below line:
self.variableA.trace("w", self.printSomeStuff)
variableA is a stringVar and is set up as shown below
self.variableA = tk.StringVar(self.detailViewHolderFrame)
self.variableA.set(self.OPTIONS0[0])
for some reason this line causes there to be a reference to the object and hence it never gets garbage collected and I end up with a memory leak.
Does anyone have any ideas on how I might get round this other than using a different widget.
I can expand on the code further but first I wanted to see if this is a common issue and if anybody know the underlying problem.
You can try to delete the callback(s) using trace_vdelete()
. Hopefully that will remove the reference(s) to the tk variable.
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()
#
You will need to figure out how to call trace_vdelete()
, possibly you can do that from the __del__()
method of the tk variable by subclassing and overriding __del__()
:
class MyStringVar(StringVar):
def __del__(self):
for t in self.trace_vinfo():
self.trace_vdelete(*t)
StringVar.__del__(self)
As Blckknght pointed out, __del__
will not be called until just before the object is garbage collected, and this will not happen when the StringVar
is being traced because an additional reference is held.
One solution is to use an external function as the trace callback.
If, however, you want the trace callback to be a method of your class, a solution is to set your class up as a context manager, i.e. a class that can be used in a with
statement. Then a cleanup method can be called when the with statement terminates. The cleanup method will delete the trace callbacks and this will remove the reference to your object from the tk trace system. It should then be available for garbage collection. Here's an example:
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)
Demo
>>> 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__()
was not called because the trace system still holds a reference to this instance of YourClass
. You could manually call the cleanup()
method:
>>> obj = YourClass()
>>> obj.cleanup()
YourClass.cleanup():
YourClass.cleanup(): deleting ('r', '139833533984192cb')
YourClass.cleanup(): deleting ('w', '139833533983552cb')
>>> del obj
YourClass.__del__():
Calling cleanup()
removes the references to the YourClass
instance and garbage collection can occur. It's easy to forget to call cleanup()
every time, and it's a pain too. Using the context manager makes calling clean up code easy:
>>> 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')
Here __exit__()
was called automatically upon exit from the with
statement and it delegates to cleanup()
. Deleting or rebinding obj
will cause garbage collection, as will the variable going out of scope for example upon return from a function:
>>> obj = None
YourClass.__del__():
One final benefit of using the context manager is that __exit()__
will always be called upon exit from the context manager, for whatever reason. This includes any unhandled exceptions, so your clean up code will always be called:
>>> 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__():