python: calling super().__init__ too early in the __init__ method?


Question

I have a class hierarchy where __init__ in class Base performs some pre-initialization and then calls method calculate. The calculate method is defined in class Base, but it's expected to be redefined in derived classes. The redefined calculate will use some of the attributes that are only available in class Derived:

class Base:
    def __init__(self, args):
        # perform some pre-initialization
        ...
        # now call method "calculate"
        self.calculate()

class Derived(Base):
    def __init__(self, args, additional_attr):
        super().__init__(args)
        # do some work and create new instance attributes
        ...
        self.additional_attr = additional_attr

This is not going to work because calculate method in class Derived will be invoked before self.additional_attr is assigned.

I can't move super().__init__(args) call to the end of the __init__ method because some of the work it does has to happen before processing additional_attr.

What to do?

1
2
4/26/2011 5:46:21 AM

Accepted Answer

Perhaps you shouldn't have the calculate() call in your constructor then. If you can't construct a derived object by allowing the base constructor to complete first, then you must be doing something wrong IMHO. A sensible approach would be to move that call out of the constructor and perhaps create a factory method to make that call automatically. Then use that method if you need precalculated instances.

class Base(object):
    def __init__(self, args):
        # perform some initialization
        pass
    def calculate(self):
        # do stuff
        pass
    @classmethod
    def precalculated(cls, args):
        # construct first
        newBase = cls(args)
        # now call method "calculate"
        newBase.calculate()
        return newBase

class Derived(Base):
    def __init__(self, args, additional_attr):
        super(Derived, self).__init__(args)
        # do some work and create new instance attributes
        self.additional_attr = additional_attr
    @classmethod
    def precalculated(cls, args, additional_attr): # also if you want
        newDerived = cls(args, additional_attr)
        newDerived.calculate()
        return newDerived

newBase = Base('foo')
precalculatedBase = Base.precalculated('foo')
newDerived = Derived('foo', 'bar')
precalculatedDerived = Derived.precalculated('foo', 'bar')
7
4/26/2011 6:18:18 AM

This is bad design, IMHO, and you're obusing the object system of Python. Consider that in other OO languages like C++, you don't even have control over the creation of base classes. The derived class's constructor calls the base constructor before your code runs. Such behavior is almost always expected of well-behaved class hierarchies, and changing it can only lead to problems.

Sure, you can do some patching (such as assigning self.additional_attr before the call to super's constructor, or other tricks), but the better way would be to change your design so that it won't require such hacks. Since you've presented an abstract example here, it's hard to give more comprehensive design advice.


Licensed under: CC-BY-SA with attribution
Not affiliated with: Stack Overflow
Icon