Python OOP – Inheritance and Polymorphism

09/01/2015

5 comments

In the last post, we have discussed abstraction and set the foundations for understanding the Pythonic OO model. In this post, you will learn about inheritance and polymorphism in Python.

Inheritance

Since python supports OOP, it has an inheritance model. Now, we have said that Python doesn’t exactly have interfaces, with the exception of abstract classes as explained in the previous post. We also explained the Python does not support interfaces, because we got duck typing in Python and thus we don’t really care about grouping unrelated types together.

However, not supporting interfaces comes with a cost, and that cost is the OO model. The pythonic OO model is a multiple inheritance model. Yep, just as you’ve heard. MULTIPLE INHERITANCE. If you were a good boy in the C# lessons, you have probably learned about the diamond diagram problem regarding multiple inheritance. I will admit, multiple inheritance has its downsides, however it seems like Guido, the BDFL, and his folk at the PSF (Python Software Foundation) have done a good job over the years to counter this problem, using something called MRO, you may have heard of it from your experience with other languages, or at the university – method resolution order.

Python’s MRO is a smart depth-first algorithm that travels the inheritance graph and collects all the base classes in the tree, depending on which class’ MRO is inspected.

Consider the following diagram:

inheritance

Do you see the problem here? What happens if I define two methods with the same name on both the parent, and the right child (Child2)? Which method is called?

Old Style Classes

The answer to the question above, is actually not trivial. See, before the new style classes were introduced at Python 2.3, the classes used a very dumb and primitive MRO algorithm, it was depth first, until it hits something that matches the description. But the problem here was that it went to the left parent’s sub-graph and traversed it completely before going to the right’s parent’s sub-graph.

Or as you can see in the following image:

old-inheritance

As you can see in the old style classes, in this scenario, the topmost Parent’s method will be called. The reason? As explained, the old, dumb algorithm goes left first, finds the first occurrence of the method and returns it.

New Style Classes

The new style classes on the other hand, have a better algorithm for solving this issue, it first calculates the MRO itself upon class object creation (that is, when the class’ metaclass __new__ is called), and then it looks for the method in that exact order. Also, new style classes have __mro__ special member that shows the class’ MRO, which is actually just a Python tuple.

The MRO in our case will be the following:

(<class ‘__main__.GrandChild’>, <class ‘__main__.Child’>, <class ‘__main__.Child2’>, <class ‘__main__.Parent’>, <type ‘object’>)

In this case, which method will be called? The Parent’s or Child2’s? The answer lies in the output of the __mro__ above, Child2’s method will be called.

new-inheritance

That concludes the hard part in multiple inheritance.

The fun part on the other hand, is that you can achieve tons of code reuse thanks to multiple inheritance. In C#, you would need an interface in order to create mixins. In Python, all you need, is just to add another superclass.

Multiple inheritance in Python has its pitfalls tho, we will get to a really major one when we start talking about metaprogramming, specifically when we meet metaclasses, but for now, the two most annoying pitfalls with multiple inheritance are:

  1. Trying, and failing to override another superclass’s method using another superclass, this happens if for instance, you have two superclasses that define the same method and a subclass that inherits from both, the superclass that comes first in the MRO will have its method called, the other will be ignored completely.
  2. When multiple inheritance get more and more complicated, they may also become extremely nasty, to the point they will crash your program. In example, consider the following script:

 

class Parent(object):
	pass

class Child(Parent):
	pass

class Child2(Parent):
	pass

class GrandChild(Child, Child2):
	pass

class GrandChild2(Child2, Child):
	pass

class GreatGrandChild(GrandChild, GrandChild2):
	pass

If you actually run the script above,  it will crash. The reason is that, for some reason (Which I admit, the Python’s C source code is kinda confusing, so I couldn’t really figure out exactly why, I will get back to you with another post once I dive deeper into the source code), the MRO algorithm for the new style classes can’t handle a situation where a great grandchild inherits from two grandchildren which have their parents in the different order. As you can see, GrandChild inherits from Child and Child2, while GrandChild2 inherits from Child2 and Child. Running this code will result in a nasty TypeError.

Selection_006

This may seem like nonsense, but it actually matters, make sure this doesn’t happen once you wander into the land of multiple inheritance.

Now that we are done with multiple inheritance, let’s talk about Polymorphism.

Polymorphism

Can you quack like a duck?

Can you quack like a duck? Cause that’s what Python’s Polymorphism is all about. Duck typing. In traditional Polymorphism, like you’d expect to see in C#, the way you look at an object (As the object itself, or as its ancestor) defines who’s method will be called.

In python on the other hand, there are no two ways to look at an object, you get an object, and that’s it. It may or may not have the method you are looking for based on whether it is defined in the object’s class definition or in one of its ancestors.
Lucky for us, Python doesn’t actually care whether your object is of type “Mouse” or “Cat” or “Mammal”. Python simply looks for the method you ask for, if it can’t find any in the object or any of its ancestors, it will raise an AttributeError and complain it can’t find the method you are looking for, that’s the essence of Duck Typing and Python’s Polymorphism.

This post, along with the previous ones, pretty much sum up the chapter and everything you need to know in order to start developing an ordinary piece of software. The next series will deal with introducing some Python libraries and very often used objects like files, streams, making HTTP requests, logging, debugging and more.

EDIT: It was rude to simply ignore the… following mechanism which is well.. I’d say its ugly as hell, but it nonetheless gives you full polymorphic capabilities.

Python actually fully supports your good ol’ classic polymorphism, there are two ways to achieve this, it seems as if they do the same, but actually they behave differently and one has a critical flaw, while the other is a bit code polluting, it is your choice which you choose to accomplish the required task.

First, the ugly:

import random

class Integer(object):
    def __init__(self, value):
        self.value = value

    def __add__(self, other):
        return Integer(self.value + int(other))

    def __iadd__(self, other):
        self.value = self.value + int(other)
        return self

    def __str__(self):
        return str(self.value)

    def __repr__(self):
        return str(self.value)

class DoubleAddingInteger(Integer):
    def __init__(self, value):
        Integer.__init__(self, value)

    def __add__(self, other):
        return DoubleAddingInteger(self.value + other * 2)

    def __iadd__(self, other):
        self.value += other * 2
        return self

class QuadrupleAddingInteger(DoubleAddingInteger):
    def __init__(self, value):
        DoubleAddingInteger.__init__(self, value)

    def __add__(self, other):
        return DoubleAddingInteger.__add__(self, other) * 2

    def __iadd__(self, other):
        DoubleAddingInteger.__iadd__(self, other) * 2
        self.value += self.value * 2
        return self

class RandomInteger(Integer):
    def __init__(self, value):
        Integer.__init__(self, value)

    def __add__(self, other):
        return RandomInteger(self.value + other * random.randint(1, 4))

    def __iadd__(self, other):
        self.value += other * random.randint(1, 4)
        return self

if __name__ == '__main__':
    print "NORMAL"
    normal_int = Integer(4)
    print normal_int
    print normal_int + 3
    print normal_int
    normal_int += 4
    print normal_int
    print
    print "DOUBLE ADDING"
    double_int = DoubleAddingInteger(3)
    print double_int
    print double_int + 6
    print double_int
    double_int += 2
    print double_int

    print "WACKY!!"
    random_int = RandomInteger(1)
    print random_int
    print random_int + 2
    print random_int
    random_int += 3
    print random_int

    print "Weird stuff starts here"
    random_2 = RandomInteger(2)
    print Integer.__add__(random_2, 3)
    quad = QuadrupleAddingInteger(3)
    print DoubleAddingInteger.__add__(quad, 5)
    Integer.__iadd__(quad, 2)
    print quad
    double_int_2 = DoubleAddingInteger(3)
    QuadrupleAddingInteger.__add__(double_int_2, 3)

In the example above, we have 4 types of “Integer”, yeah, how lame eh? Well at any rate.

This is what we call the “BaseClass.method” way, it basically means, name the base class of a certain class, call a method that exists on it and it will perform that method of the specified base class with the supplied context that can be an instance of the base class or of any derived subclass.

If you run the code as it is, you will see funny stuff going on and it will eventually crash.
The first part up to the weird stuff is all fine, but take a look at that piece of code from the weird stuff and on.

You can explicitly call methods on parents and pass in child instances, as you can see with QuadrupleAddingInteger (and actually with RandomInteger as well), the method that will be called is of course, the method of the specified parent.

Just to remind you the most important rule of inheritance and polymorphism, you can treat a child as one of its ancestors, the reverse isn’t possible, the same applies in Python. The last line will explode with spectacular error!

Selection_012

Now take a look at the same code, in the SUPER way, it is more elegant, but has a fatal flaw.

import random

class Integer(object):
    def __init__(self, value):
        self.value = value

    def __add__(self, other):
        return Integer(self.value + int(other))

    def __iadd__(self, other):
        self.value = self.value + int(other)
        return self

    def __str__(self):
        return str(self.value)

    def __repr__(self):
        return str(self.value)

class DoubleAddingInteger(Integer):
    def __init__(self, value):
        super(DoubleAddingInteger, self).__init__(value)

    def __add__(self, other):
        return DoubleAddingInteger(self.value + other * 2)

    def __iadd__(self, other):
        self.value += other * 2
        return self

class QuadrupleAddingInteger(DoubleAddingInteger):
    def __init__(self, value):
        super(QuadrupleAddingInteger, self).__init__(value)

    def __add__(self, other):
        return super(QuadrupleAddingInteger, self).__add__(other) * 2

    def __iadd__(self, other):
        super(QuadrupleAddingInteger, self).__iadd__(self, other) * 2
        self.value += self.value * 2
        return self

class RandomInteger(Integer):
    def __init__(self, value):
        super(RandomInteger, self).__init__(value)

    def __add__(self, other):
        return RandomInteger(self.value + other * random.randint(1, 4))

    def __iadd__(self, other):
        self.value += other * random.randint(1, 4)
        return self

if __name__ == '__main__':
    print "NORMAL"
    normal_int = Integer(4)
    print normal_int
    print normal_int + 3
    print normal_int
    normal_int += 4
    print normal_int
    print
    print "DOUBLE ADDING"
    double_int = DoubleAddingInteger(3)
    print double_int
    print double_int + 6
    print double_int
    double_int += 2
    print double_int

    print "WACKY!!"
    random_int = RandomInteger(1)
    print random_int
    print random_int + 2
    print random_int
    random_int += 3
    print random_int

    print "Weird stuff starts here"
    random_2 = RandomInteger(2)
    print super(RandomInteger, random_2).__add__(3)
    quad = QuadrupleAddingInteger(3)
    print super(QuadrupleAddingInteger, quad).__add__(5)
    super(DoubleAddingInteger, quad).__iadd__(2)
    print quad
    double_int_2 = DoubleAddingInteger(3)
    QuadrupleAddingInteger.__add__(double_int_2, 3)

Well, the result is the same, but something completely different happens here, and it has a fatal flaw and other flaws, can you guess it?

Maybe this example will help (and it is Python3 friendly!):

class Parent(object):
    name = 'Bob'

    def do_something(self):
        print('My name is {}'.format(self.name))

class Child(Parent):
    name = 'Steve'

    def do_something(self):
        print("Ahoy, Matey! I am {} the code pirate!".format(self.name))

class OtherChild(Parent):
    name = 'Murphey'

    def do_something(self):
        print("My name is {} and I am here to impose terrible luck upon thou".format(self.name)) # I am reading the last book of The Malloreon at the moment. Arends are pretty entertaining

class OtherOtherChild(Parent):
    name = 'Gogo'

    def do_something(self):
        print("My name is {}, the malicious creature of the night".format(self.name))

class GrandChild(Child, OtherChild, OtherOtherChild):
    name = 'Joe'

    def do_something(self):
        print("Grab a cup of {}".format(self.name))

if __name__ == '__main__':
    c = Child()
    c.do_something()
    gc = GrandChild()
    gc.do_something()
    super(Child, gc).do_something()
    super(OtherChild, gc).do_something()

Can you guess the output of the script above?

Here is what may has come up your mind:

Ahoy, Matey! I am Steve the code pirate!
Grab a cup of Joe
My name is Joe
My name is Joe

This is not the actual output!

Here is the correct output:

Ahoy, Matey! I am Steve the code pirate!
Grab a cup of Joe
My name is Joe and I am here to impose terrible luck upon thou
My name is Joe, the malicious creature of the night

Does this make any sense? no? Well neither to me when I found it out.
Apparently Python multiple inheritance is a bit wacko. If you recall, every class has its MRO, it defines the order of which methods are called when you traverse the inheritance tree.
the super function does something very unexpected, what one would expect is that super would give result with invoking a function on a subclass’ parent, but this is not true.
What super actually does is this, when calling super with Parent class and child instance:

So when using the single inheritance model, you will always get the parent of the class supplied as an argument to the super function, because it will appear on the subclass MRO after the class supplied.

When using the multiple inheritance model, a class may inherit from more than one parent and thus it will have more classes in its MRO before the grandparent class.

What actually happens in the script then, is this:

    super(Child, gc).do_something()
  1. Get GrandChild’s MRO
  2. Locate Child in GrandChild’s MRO
  3. Pick the next class after Child in GrandChild’s MRO
  4. Call OtherChild.do_something
    super(OtherChild, gc).do_something()
  1. Get GrandChild’s MRO
  2. Locate OtherChild in GrandChild’s MRO
  3. Pick the next class after Child in GrandChild’s MRO
  4. Call OtherOtherChild.do_something

This is BAD BAD BAD. It creates a lot of confusion and it not straightforward at all. The only good thing I can say about super is that its syntax is somewhat less hacky than baseclass.method. In Python3 it is a bit more attractive, as you can actually call super() (without arguments) inside a certain class’ scope and it will get you the next class on the MRO.

Super VS baseclass.method

To summarize super and baseclass –

baseclass.method

  1. More explicit
  2. Syntax is kinda ugly.
  3. No “surprises” in multiple inheritance.

super

  1. More implicit
  2. Better syntax, especially in Python 3.
  3. Can be confusing with multiple inheritance.

My rule of thumb, use super when using single inheritance, it is more elegant, use baseclass.method when using multiple inheritance.
If you think this is nonsense, consider the possibility of a junior programmer joining your team, someone who has not yet dived into Python deep enough to experience super’s problems. Using super will send that new programmer into wasting at least 3 hours on each class that has multiple inheritance, trying to figure out why super doesn’t work as he thinks it should. In that case, I would gladly litter my code with explicit baseclass.method calls.

I believe that this really does wrap up things regarding Python’s inheritance and polymorphism.

Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

5 comments

  1. Hadoop Training Chennai04/06/2015 ב 12:32

    Its really usefull information…thanks for sharing

    Reply
    1. agnes07/09/2015 ב 17:04

      Inheritance provides a mechanism that allows us to build on our … We call this polymorphism or overriding and it is useful

      Reply
  2. ios Training in Chennai05/10/2015 ב 11:40

    Really enjoying your post, you have a great teaching style and make these new concepts much easier to understand. Thanks.

    http://www.trainingintambaram.in/ios-training-in-chennai.html

    Reply
  3. oracle training in chennai31/10/2015 ב 07:55

    Very good hard work.you share to the this information is very useful.i’m very learning python studied details.

    oracle training in chennai

    Reply
  4. maheswari20/02/2016 ב 13:07

    Thank you for information..keep sharing..its really useful.

    Reply