We have some API that should be shut down (e.g. api.shutdown()) just once per python process and specific only to a particular class (e.g. ControllerA) from a hierarchy of controllers (e.g. Controller inherited by ControllerA, ..., ControllerZ). Can I add a "destructor logic" in python in some reasonable way when destroying the class itself and not just any of its instances? I understand that in Python classes are not explicitly destroyed in the same way as objects but rather garbage collected when there are no existing references to them but perhaps there is some way to achieve the above effect? What I want is to perform the api.shutdown() call once for all instances but not explicitly as it should not be done for instances of ControllerB ..., ControllerZ. Could using metaclasses or something like metaclass destructors achieve anything like it?
2 Answers
You could use a shutdown hook with atexit. Inside the class definition, add the appropriate function for atexit.
However, note that it is hard to design your software around when such "cleanup" methods, like atexit or __del__, will be called.
It can be a better design to have an explicit lifecycle in your application, so that at shutdown, a list of registered callback functions is called.
1 Comment
api.shutdown() that we have to call first. So essentially this solution is turning into a chicken-or-egg problem.You don't need metaclasses for that - just a classmethod.
metaclasses actually cant even help - since their __del__ method won't be called ever: any created class in Python is kept forever while the process is alive. (probably it should be possible to track all the references down and erase them, but it is not done by default with the class simply going out of scope)
Just have a class level registry - a list as a class attribute - and append all crrated instances there. this could be done in __init__ but also in a metaclass __call__ method, if you'd really use a metaclass.
and any code now could just go through this registry and close whatever is needed in all instances. There could be a classmethod to do it, called from your shutdown itself:
class Base:
def __init__(self):
cls = type(self)
if not "_instances" in cls.__dict__:
# check cls.__dict__ instead of using "hasattr" as we want
# one registry in each subclass.
# "hasattr" would return `True` for any superclass having a "._instances" attribute.
cls._instances = set()
cls._instances.add(self)
@classmethod
def shutdown(cls):
for instance in cls._instances:
instance.close()
def close(self):
raise NotImplementedError()
...
class ControllerA(Base):
...
def close(self):
# example:
try:
self.connection.close()
except Exception as error:
logger.info(f"Error closing connection: {error}")
# maybe it is already closed, etc...
# so we just make some noise to ensure it is visible, if needed.
9 Comments
api.shutdown() only once per python process and not once for each instance.atexit as you explicit mention that other classes than the one of interest should not be closed. atexit hooks are called just at interpreter shutdown, so no other classes will possibly continue to keep anything open.Base.shutdown() at exactly once at the end of a program while my preference was to not have to call anything and find a natural destructor-like way for the cleanup to happen implicitly when it is time without such an explicit call. Note that if one accepts having the explicit call, we could just call a base class method close once at the end of the program (which will do api,shutdown() for a particular sublcass or nothing for the rest) without tracking any instances (something like type(controller).close().__del__ that it is not reliable (and for the class case, it won't even work not even occasionally). If you want lifecycle events without having to call some explicit .close method, you have to implement the "Context manager protocol" by having your objects have the __enter__ and __exit__ methods, and use a with statement block.