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__.

1 Like

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?

1 Like

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

@stoneleaf it looks like the link for “this Stackoverflow question” is actually just a link back to here, do you remember which SO question you meant to reference?

I ask because I’ve been trying to figure out how __init_subclass__ could be used to implement a post-init method that runs after an object’s whole tower of __init__ methods has run, and I can’t quite see how to manage it.

I do not, and I think I misunderstood the question – __init_subclass__ is for class creation but I think the OP was talking about instance creation.

This would actually be useful for implementing my proposal of abstract attributes: [abc] Add abstract attributes via `abstract` type-hint

In essence, __post__ could be used by libraries to validate class instances, after they have been created. (abstract base classes and pydantic come to mind)

The issues with the solutions proposed so far in this thread is that they require the user to call super()___init__ last in their own __init__. However, sometimes it is necessary to call super().__init__ early. The huge advantage of __post__ would be that it will be guaranteed that it is only called after __init__ and potential super().__init__ are resolved.

That is simple to do with a custom metaclass - maybe you could just write one for your project?

Having a separate __new__ and __init__ stage is great and works out of the box - when one have to explain the mechanisms and "why"s, not so great anymore. Given that the language already can allow one to have as many stages of class initalizations as one wants by writting a custom __call__ method in the metaclass, I don’t see the need to add still another stage there.

Since it looks like no one had mentioned the way of getting what you want with a metaclass, here it goes:

class MyMeta(type):
     def __call__(cls, *args, **kwargs):
            # here is "before __new__ is called"
            instance = super().__call__(*args, **kwargs)
            # here is "after __new__ and __init__"
            if (post:=getattr(cls, "__post_init__", None):
                   post(*args, **kwargs)
            return instance

class MyBase(metaclass=MyMeta):
     ...

It would be though to do it with __init_subclass__ - because one would have to decorate the most-derived __init__ method, while making sure the “post init” is not called after parent-class __init__s are called.
It is trivial to do in the metaclass __call__, however.