pevogam
pevogam

Reputation: 608

Executing code on class (metaclass instance) destruction in python

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?

Upvotes: 0

Views: 56

Answers (2)

jsbueno
jsbueno

Reputation: 110591

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. 

Upvotes: 0

Joshua Fox
Joshua Fox

Reputation: 19695

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.

Upvotes: 0

Related Questions