Add a __post__ method, equivalent to the __new__method, but called after __init__?

I author a package (openpnm) that provides some generic classes, which we expect users to subclass, and I’ve recently come to wish that our generic classes could run some code AFTER the subclass has been initialized.

At the moment, the user writes their code (e.g. defining some constants, etc) in the __init__ of their subclass, and the only ‘trick’ they need to remember is to call super().__init__() at the top of the method.

But I would really like to be able to ‘clean’ up the initialization by running some code post-initialization. My exact use case is pretty convoluted, so I won’t dive into the details…but it did get me wondering if this has ever been considered by anyone else. I found some stackoverflow Q&As on this, but the solutions all suggest defining a metaclass, which seems to adds extra onus on the user (in addition to calling super.__init__()), so I did’t love that approach.

It seems to me that something like a __post__ method could be implemented and called automatically by python, and I could put my ‘clean-up/housekeeping’ code in there, and the user would never know. I feel like this is analogous the __new__ method, which gets called BEFORE __init__.

I don’t know if this comes up often enough to warrant adding another hook as part of the class creation. Could you do this with a metaclass that wraps a class’ __init__ method such that it calls any defined __post__ method?

I am sure it’s not that common…I have been developing in python for about 10 years and this is the first time I’ve needed something like this :slight_smile: It is probably the result of me coding something in a silly way.

…anyway…I am not too familiar with the use of metaclasses…could you perhaps give a minimal snippet? (the stackoverflow answers that I found were not minimal imo).

My “idea” would be something like this:

class SubClass(GenericClass):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        # user does some stuff here

        # Now I want to call a 'clean-up' method, which could be done explicitly like this:
        self.__post__()
        # However, I don't trust the user to know or remember to do this

Perhaps the post_init method of dataclasses could be useful for this?
See stackoverflow or docs

:open_mouth: I had no idea this was already implemented on the data class! I will considered using the data class for my purposes…although I must point out that my suggestion might not be that crazy.

At the moment, the user writes their code (e.g. defining some
constants, etc) in the init of their subclass, and the only
‘trick’ they need to remember is to call super().init() at the top
of the method.

Why not tell them to call super().__init__() at the end of the
method, after they have run their own init code?

Put your setup code in __new__. The user sets their own initialisation
code in __init__ and then calls your __init__ last for cleanup.

class Parent(object):
    def __new__(cls, *args):
        instance = super().__new__(cls)
        # Perform your initialisation here.
        instance.a = "something"
        instance.b = 9999
        return instance

    def __init__(self, *args):
        # Cleanup here.
        ...


class Child(object):
    def __init__(self, *args):
        assert self.a == "something" and self.b == 9999
        self.x = "and another thing..."
        # Mandatory to call this last.
        super().__init__(*args)

Obviously this is a backwards-incompatible change for your users, but
it’s not a very large one, requiring a change to only a single line of
code.

Users can even write version independent code if they need to support
both versions:

def __init__(self, *args):
    # Check for a flag? Or a version number on the module?
    postinit = hasattr(Parent, 'postinit')
    if not postinit:
        # Call init *first*
        super().__init__(*args)
    ...
    if postinit:
        # Call init *last*
        super().__init__(*args)

But I would really like to be able to ‘clean’ up the initialization by
running some code post-initialization.

Alternatively, you can just tell your users to call super().__init__
at the start of their init method and super()._post at the end.

Note that you should not use a dunder like __post__ as such names are
reserved for use by Python in the future.

Check out __init_subclass__ from PEP 487.

Some pointers in this Stackoverflow question