Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calling super().__init__() gives the wrong method when it is overridden

I have a base class and a sub class. In the base class I have an instance method which is called in __init__. In the sub class I have overridden this method. The super() call in the sub class does not call the original base method but the overridden sub class method. Why?

class BaseClass:
    def __init__(self, value):

        self.value = value
        self.transformed_value = self.create_value(value)

    def create_value(self, value):

        print("base")
        return value + 1

class SubClass(BaseClass):
    def __init__(self, value):

        super().__init__(value)

    def create_value(self, value):
        print("sub")
        return value + 2

s = SubClass(3)

I expect the print output to be "base", but the actual output is "sub". How can I modify the code to get "base" without explicitly calling BaseClass.create_value(self, value) in the __init__ of BaseClass?

like image 803
kanilor Avatar asked Jul 15 '19 15:07

kanilor


2 Answers

Python is dynamically typed. That means when you write

def __init__(self, value):
    self.value = value
    self.transformed_value = self.create_value(value)

is doesn't matter which class that method belongs to when evaluating self.create_value: it only matters what the type of self is when the method is called.

When you call SubClass(3), you eventually call SubClass.__init__, which immediately calls BaseClass.__init__ with an argument of type SubClass. Since SubClass.create_value is defined, that's what self.create_value resolves to.

If, for whatever reason, you insist the BaseClass.__init__ calls BaseClass.create_value on its self argument, you have to do so explicitly with BaseClass.create_value(self, value). However, that's rarely a good idea. If the type of self wants a method it overrides to be called, that's its responsibility to do so, by using super itself:

def create_value(self, value):
    rv = super().create_value()
    print("sub")
    return value + 2  # Or perhaps do something with rv first?
like image 184
chepner Avatar answered Oct 18 '22 16:10

chepner


Further to add to @chepner's elegant answer, if you want the parent class to call it's own method, you may modify the parent class as below:

class BaseClass:

    def __init__(self, value):
        self.value = value
        self.transformed_value = self.base_create_value(value)     # use the renamed reference

    def create_value(self, value):
        print("base")
        return value + 1

    base_create_value = create_value   # create a local reference to the method

In the above BaseClass, we are creating a simple reference to a parent method and are using the same inside its own __init__ method. By doing so, we can continue to make use of this reference object even if the base class' method is overridden and when it is called by the child instance.

like image 42
Swadhikar Avatar answered Oct 18 '22 17:10

Swadhikar