xorsyst
xorsyst

Reputation: 8247

Is there any way to have a base-class method called after the child's init?

I have a class hierarchy - which may be multiple inheritance, for example:

class Base:
  def __init__(self):
    print("Base")

class A(Base):
  def __init__(self):
    super().__init__()
    print("A")

class B(Base):
  def __init__(self):
    super().__init__()
    print("B")

class C(A, B):
  def __init__(self):
    super().__init__()
    print("C")

x = C()

will print

Base
B
A
C

as expected.

what I'd like to do is somehow print Done after this is all finished, by defining a method or similar in Base that will be called when C.__init__() has completed.

What I can't do:

  1. Put code in Base.__init__ as that gets called too early.
  2. Put code in C.__init__ as I want this to apply to all potential subclasses

I've tried various things with metaclasses and haven't managed to make anything work yet - any clever ideas?

Upvotes: 0

Views: 36

Answers (1)

chepner
chepner

Reputation: 531215

The call to __init__ is handled by the call to type.__call__, once __new__ has returned. You could define a metaclass that overrides __call__ to handle your post-init code; I believe this is the only way to avoid having the code be called prematurely whenever you subclass, since __call__ won't be invoked for or by any of the ancestor classes.

class PostInitHook(type):
    def __call__(cls, *args, **kwargs):
        rv = super().__call__(*args, **kwargs)
        print("Done")
        return rv

class Base(metaclass=PostInitHook):
    def __init__(self):
        print("Base")

...

More generally, you could replace print("Done") a class specific hook, for example,

class PostInitHook(type):
    def __new__(metacls, *args, **kwargs):
        cls = super().__new__(metacls, *args, **kwargs)
        try:
            cls._post_init
        except AttributeError:
            raise TypeError("Failed to define _post_init")
        return cls

    def __call__(cls, *args, **kwargs):
        rv = super().__call__(*args, **kwargs)
        rv._post_init()
        return rv

class Base(metaclass=PostInitHook):
    def __init__(self):
        print("Base")

    def _post_init(self):
        print("Done")

Then

>>> a = A()
Base
A
Done
>>> b = B()
Base
B
Done
>>> c = C()
Base
B
A
C
Done
>>> class D(C):
...   def __init__(self):
...     super().__init__()
...     print("D")
...
>>> d = D()
Base
B
A
C
D
Done
>>>

Upvotes: 3

Related Questions