How to perform common post-initialization tasks in inherited classes?
Asked Answered
B

6

23

The initialization process of a group of classes that share a common parent can be divided into three parts:

  • Common initialization
  • Subclass-specific initialization
  • Common post-initialization

Currently the first two parts are called from the __init__ method of each child class, but the final post-initialization part has to be called separately, for example

class BaseClass:
    def __init__(self):
        print 'base __init__'
        self.common1()

    def common1(self):
        print 'common 1'

    def finalizeInitialization(self):
        print 'finalizeInitialization [common2]'


class Subclass1(BaseClass):
    def __init__(self):
        BaseClass.__init__(self)
        self.specific()

    def specific(self):
        print 'specific'


if __name__ == '__main__':
    s = Subclass1()  # Don't forget to finalize the initialization
    s.finalizeInitialization()  # now the object is fully initialized

Is there a way to not to have to call finalizeInitialization()? Or one can transfer the call to finalizeInitialization() into Subclass1's __init__ (as in S.Lott's answer). This makes the life easier, but still one has to remember to complete the initialization, this time inside the "constructor". Either way there is no way to enforce full initialization, which is what I'm looking for.

Belew answered 27/4, 2009 at 20:38 Comment(0)
D
10

Version 1 - delegate everything.

class Subclass1(BaseClass):
    def __init__(self):
        super( Subclass1, self ).__init__()
        self.specific()
        super( Subclass1, self ).finalizeInitialization()

Version 2 - delegate just one step

class BaseClass:
    def __init__(self):
        print 'base __init__'
        self.common1()
        self.specific()
        self.finalizeInitialization()

    def common1(self):
        print 'common 1'

    def finalizeInitialization(self):
        print 'finalizeInitialization [common2]'

    def specific( self ):
        # two choices:
        # if this is "abstract": raise an exception
        # if this is "concrete": pass
Digitalize answered 27/4, 2009 at 20:43 Comment(0)
A
14

Template Method Design Pattern to the rescue:

class BaseClass:
    def __init__(self, specifics=None):
        print 'base __init__'
        self.common1()
        if specifics is not None:
            specifics()
        self.finalizeInitialization()

    def common1(self):
        print 'common 1'

    def finalizeInitialization(self):
        print 'finalizeInitialization [common2]'


class Subclass1(BaseClass):
    def __init__(self):
        BaseClass.__init__(self, self.specific)

    def specific(self):
        print 'specific'
Artois answered 28/4, 2009 at 1:17 Comment(1)
This is dangerous: a user might add code after BaseClass.__init__ and it will test before the class is fully initialized.Mitchmitchael
C
14

You can use metaclasses (and updated to Python3 code):

class PostInitCaller(type):
    def __call__(cls, *args, **kwargs):
        obj = type.__call__(cls, *args, **kwargs)
        obj.__post_init__()
        return obj


class BaseClass(metaclass=PostInitCaller):  

    def __init__(self):
        print('base __init__')
        self.common1()

    def common1(self):
        print('common 1')

    def finalizeInitialization(self):
        print('finalizeInitialization [common2]')

    def __post_init__(self): # this is called at the end of __init__
        self.finalizeInitialization()

class Subclass1(BaseClass):
    def __init__(self):
        super().__init__()
        self.specific()

    def specific(self):
        print('specific')


s = Subclass1() 
base __init__
common 1
specific
finalizeInitialization [common2]
Conductivity answered 31/10, 2021 at 23:6 Comment(3)
The most elegant solutionKaminsky
Also, more robust than the other solutions, because they rely on the user remembering to call the base class init at the correct position.Mitchmitchael
Nice - could we use __new__ instead of __call__ override in PostInitCaller?Footsore
D
10

Version 1 - delegate everything.

class Subclass1(BaseClass):
    def __init__(self):
        super( Subclass1, self ).__init__()
        self.specific()
        super( Subclass1, self ).finalizeInitialization()

Version 2 - delegate just one step

class BaseClass:
    def __init__(self):
        print 'base __init__'
        self.common1()
        self.specific()
        self.finalizeInitialization()

    def common1(self):
        print 'common 1'

    def finalizeInitialization(self):
        print 'finalizeInitialization [common2]'

    def specific( self ):
        # two choices:
        # if this is "abstract": raise an exception
        # if this is "concrete": pass
Digitalize answered 27/4, 2009 at 20:43 Comment(0)
N
1

Similar to S. Lott's approach, except there's no way (short of overriding __init__) for the derived classes to override (or even call) the common methods:

class BaseClass:
    def __init__(self):
        def common():
            print "common initialization..."

        def final():
            print "common finalization..."

        common()
        self.specific()
        final()

    def final_init(self):
        print "BaseClass.final_init"


class Subclass1(BaseClass):

    def specific(self):
        print "Subclass1.specific"

You might want to provide a default implementation of specific in BaseClass if it's not okay to raise an AttributeError when you create an instance of any subclass that doesn't provide its own implementation.

Narghile answered 28/4, 2009 at 2:10 Comment(0)
V
0

What's wrong with calling finalInitilazation from the Subclass's init?

   class BaseClass:
        def __init__(self):
            print 'base __init__'
            self.common1()

        def common1(self):
            print 'common 1'

        def finalizeInitialization(self):
            print 'finalizeInitialization [common2]'


    class Subclass1(BaseClass):
        def __init__(self):
            BaseClass.__init__(self)
            self.specific()
            BaseClass.finalizeInitialization(self)

        def specific(self):
            print 'specific'


    if __name__ == '__main__':
        s = Subclass1() #Don't forget to finalize the initialization
        s.finalizeInitialization()  # now the object is fully initialized
Volga answered 27/4, 2009 at 20:47 Comment(0)
F
0

If you have to call multiple methods in a specific order it typically means that the design has problems to begin with (it's leaking implementation detail). So I would try to work from that end.

On the other hand if people derive from the class and have to modify the initialisation they should be aware of the implications - it is not something you would want to have in your normal API. Alternatively you could be defensive about the final initialization and check that it has been called in methods that depend on it (to call it or raise an exception if not).

Famed answered 27/4, 2009 at 21:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.