__del__ isn't called when expected
The goal I'm trying to accomplish is a class that cleans up after itself when the object is no longer in use. Users (i.e: me) sometimes forget to clean up their own messes.
__del__ seemed to be the right way to implement this, but it isn't working as expected.
I have the following module (test_helpers.py):
class NoisyThing:
def __init__(self, name):
log.info(f'starting NoisyThing {name}')
self.name = name
self.counter = 0
self.trigger_func = None
self.startup()
@pyscript_compile
def __del__(self):
self.trigger_func = None
def startup(self):
@time_trigger("period(now, 1sec, now + 30sec)")
def be_noisy():
self.counter = self.counter + 1
log.info(f'{self.name} counter {self.counter}')
self.trigger_func = be_noisy
If used like this, by reloading (just saving the file again with no changes) you can see by the log output the old object and trigger functions are not destroyed. Reloading test_helpers, however, does stop the noise:
from test_helpers import NoisyThing
a = NoisyThing('a')
Ideally, the above would work as the goal is to make NoisyThing clean up after itself. However, this (which doesn't accomplish the goal) also doesn't work:
from test_helpers import NoisyThing
a = NoisyThing('a')
@time_trigger('shutdown')
def shutdown():
# without global a "name 'a' is not defined" NameError occurs
global a
del a
The only way to make it work is to call it directly:
from test_helpers import NoisyThing
a = NoisyThing('a')
@time_trigger('shutdown')
def shutdown():
a.__del__()
Perhaps, when a context is reloaded/destroyed, all of the variables in that context could be iterated and explicitly deled.
Another solution (not for object destruction in general, but for handling trigger functions created like this) might be to have a pyscript method explicitly for registering trigger functions in the current context. This would allow me to replace the last line of startup with func.register(be_noisy).
Another solution (that might break other things?) is to have modules run in the context of whatever is calling it. That way they are destroyed when the calling script is destroyed, instead of being tied to the module itself.
It's also worth noting that returning the trigger functions doesn't clean up the mess either:
test_helpers.py
class NoisyThing:
def __init__(self, name):
log.info(f'starting NoisyThing {name}')
self.name = name
self.counter = 0
def startup(self):
@time_trigger("period(now, 1sec, now + 30sec)")
def be_noisy():
self.counter = self.counter + 1
log.info(f'{self.name} counter {self.counter}')
return be_noisy
test_script.py
from test_helpers import NoisyThing
a = NoisyThing('a')
a_triggers = a.startup()
Like #281 I suspect this may've been caused by #457, so it could be fixed now that Craig has resolved that issue.