Thomas
Thomas

Reputation: 211

Python Polymorphism

I have an assignment from an instructor trying to teach OOP in python. I'm pretty familiar with OOP in C++ and C# but having a difficulty figuring out what is going on in some of my code. It works and both classes function as I want but I had to add some weird code for it to work and I don't understand why.

Specifically referencing the code starting in line 64

class Cone(Cylinder):

    #Constructor
    def __init__ (self, radius, height):        
        Cylinder.__init__(self, radius, height)
        self.calcVolume()


    def calcVolume(self):
        Cylinder.calcVolume(self)
        self.__volume = Cylinder.GetVolume(self) * (1.0/3.0)

So when implementing Cone I don't understand why the Cylinder.calcVolume() is not called when the cone's constructor calls the cylinder's constructor. The code would make more sense to me if it was implicitly called but im forced to call the method explicitly. Some guidance or explanation would be awesome.

After Posting I made this change does it make more sense?

class Cone(Cylinder):

    #Constructor
    def __init__ (self, radius, height):        
        Cylinder.__init__(self, radius, height)
        self.calcVolume()


    def calcVolume(self):
        self.__volume = self.GetBase().GetArea() * self.GetHeight() * (1.0/3.0)

Upvotes: 3

Views: 2909

Answers (2)

dnozay
dnozay

Reputation: 24304

This is what happens when you call Cone.__init__():

  • it performs Cylinder.__init__(),
  • in turns it calls self.calcVolume(),
  • because of inheritance, the resolution order finds the method on the Cone type,
  • it calls Cone.calcVolume() instead of Cylinder.calcVolume().

During __init__() I think you want to call:

  • Cone.calcVolume(self) in Cone.__init__(), or
  • Cylinder.calcVolume(self) in Cylinder.__init__().

Of course, if you were using new style classes (inheriting from object) then you could just use type(self).calcVolume(self); however type(self) on an old-style class is going to give you the instance type instead of the actual class, which isn't going to work in your case.


full example:

class Circle():
    #Constructor
    def __init__ (self, radius):
        self.__radius = radius
        self.calcArea()
    def calcArea(self, PI = 3.14):
        self.__area = (self.__radius**2) * PI
    #Get Functions
    def GetArea(self):
        return self.__area
    def GetRadius(self):
        return self.__radius
    #Set Functions
    def SetRadius(self, radius):
        self.__radius = radius
        self.calcArea()

class Cylinder():
    #Constructor
    def __init__(self, radius, height):
        self.__height = height
        self.__base = Circle(radius)
        Cylinder.calcVolume(self)
    def calcVolume(self):
        self.__volume = self.__base.GetArea() * self.__height
    #Get Functions
    def GetVolume(self):
        return self.__volume
    def GetBase(self):
        return self.__base
    def GetRadius(self):
        return self.__base.GetRadius()
    def GetHeight(self):
        return self.__height
    #Set Functions
    def SetRadius(self, radius):
        self.__base.SetRadius(radius)
        self.calcVolume()
    def SetHeight(self, height):
        self.__height = height
        self.calcVolume()


class Cone(Cylinder):
    #Constructor
    def __init__ (self, radius, height):
        Cylinder.__init__(self, radius, height)
        Cone.calcVolume(self)
    def calcVolume(self):
        Cylinder.calcVolume(self)
        self.__volume = Cylinder.GetVolume(self) * (1.0/3.0)
    #Get Functions
    def GetVolume(self):
        return self.__volume
    #Set Functions
    def SetRadius(self, radius):
        Cylinder.SetRadius(self, radius)
        self.calcVolume()
    def SetHeight(self, height):
        Cylinder.SetHeight(self, height)
        self.calcVolume()


def main():
        cylinder = Cylinder(5, 6)
        cone = Cone(5, 6)
        circle = Circle(5)
        print cylinder.GetVolume()
        print cone.GetVolume()
        print circle.GetArea()
        cone.SetHeight(7)
        print cone.GetVolume()

main()

Upvotes: 3

Tadeck
Tadeck

Reputation: 137290

Rule is pretty straightforward: when the method is to be called, its name is resolved according to Method Resolution Order (it is tricky, as Python has multiple inheritance).

After the name is resolved, Python stops searching (I am simplifying). It is up to the specific method to call parent class's method (or any other method). Doing it implicitly could be a common cause of problems (eg. when you want to override some method from parent class).

Also, to invoke parent method use super(). Instead of writing this in Cone class:

Cylinder.__init__(self, radius, height)

write this:

super(Cone, self).__init__(self, radius, height)

That makes it more flexible: it just delegates finding proper method to MRO, which then checks parent classes in specific order (in this case, it will indeed find Cylinder.__init__).

More reading:

Upvotes: 3

Related Questions