What’s the point of inheritance in python ?

Python is a fascinating language. It makes you think. Sometimes it can destroy your beliefs.

My background is in statically typed languages. I came from C++, camped there for a while, then moved to python. I also explored Java recently.

I would like to present my case by remembering the three pillars of object oriented programming:

  1. Encapsulation
  2. Inheritance
  3. Polymorphism

In particular, I would like to go into the details of two of them: inheritance and polymorphism.

Inheritance is a strategy that allows you to define a new class (hereafter called NEW) as derived of an old one (OLD). The new class gets access to all methods and attributes of the old one, and according to the substitution principle, you can replace objects of type OLD with object of type NEW without altering the properties of the program. For example, suppose you have the following situation

#include <iostream>

class Animal {
public:
    virtual void speak() = 0;
};

class Dog : public Animal {
    void speak() { std::cout << "woff!" <<std::endl; }
};

class Cat : public Animal {
    void speak() { std::cout << "meow!" <<std::endl; }
};

void makeSpeak(Animal &a) {
    a.speak();
}

int main() {
    Dog d;
    Cat c;
    makeSpeak(d);
    makeSpeak(c);
}

As you can see, makeSpeak is a routine that accepts a generic Animal object. In this case, Animal is quite similar to a java interface, as it contains only a pure virtual method. makeSpeak does not know the nature of the Animal it gets passed. It just sends it the signal “speak” and leaves the late binding to take care of which method to call: either Cat::speak() or Dog::speak(). This means that, as far as makeSpeak is concerned, the knowledge of which subclass is actually passed is irrelevant.

But what about python? Let’s see the code for the same case in python. Please note that I try to be as similar as possible to the C++ case for a moment:

class Animal(object):
    def speak(self):
        raise NotImplementedError()

class Dog(Animal):
    def speak(self):
        print "woff!"

class Cat(Animal):
    def speak(self):
        print "meow"

def makeSpeak(a):
    a.speak()

d=Dog()
c=Cat()
makeSpeak(d)
makeSpeak(c)

Now, in this example you see the same strategy. You use inheritance to leverage the hierarchical concept of both Dogs and Cats being Animals. Here is the nice thing: in python, there’s no need for this hierarchy. This works equally well

class Dog:
    def speak(self):
        print "woff!"

class Cat:
    def speak(self):
        print "meow"

def makeSpeak(a):
    a.speak()

d=Dog()
c=Cat()
makeSpeak(d)
makeSpeak(c)

In python, this kind of inheritance application is irrelevant because there’s no concern of the type. you can send the signal “speak” to any object you want. If the object is able to deal with it, it will be executed, otherwise it will raise an exception. Suppose you add a class Airplane to both codes, and submit an Airplane object to makeSpeak. In the C++ case, it won’t compile, as Airplane is not a derived class of Animal. In the python case, it will raise an exception at runtime, which could even be an expected behavior.

On the other side, suppose you add a MouthOfTruth class with a method speak(). In the C++ case, either you will have to refactor your hierarchy, or you will have to define a different makeSpeak method to accept MouthOfTruth objects… or in java you could extract the behavior into a CanSpeakIface and implement the interface for each … There are many solutions, but I wont’ go into this direction.

What I’d like to point out is that I haven’t found a single reason yet to use inheritance in python: you don’t need to implement a base-derived hierarchy to perform polymorphically. See also this post which points out a similar point of view.

So, if interface-only inheritance has no meaning, maybe it could be ok for implementation-based inheritance? What about recycling the code and the data of a base class ? You can accomplish the same through a containment relationship, with the added benefit that you can alter it at runtime, and you clearly define the interface of the contained, without risking unintended side effects.

But yes, there’s a natural case where a class hierarchy is indeed useful: Exceptions. Suppose your code is like this

class ServiceException(Exception): pass
class TrivialException(Exception): pass

class OutOfPaperException(TrivialException): pass
class OutOfTonerException(TrivialException): pass
class BrokenCircuitryException(ServiceException): pass

def usePrinter():
    raise OutOfTonerException()

def writeLocalAdmin():
    print "writing to local admin"
def writeServiceShop():
    print "writing to service shop"

try:
    usePrinter()
except TrivialException:
    writeLocalAdmin()
except ServiceException:
    writeServiceShop()

Now, in this case, you actually gain something. Suppose the printer raises OutOfTonerException. This exception will be caught by the try/except, and by virtue of this exception being hierarchically a TrivialException, it will write the local admin that something is wrong. Except for this case, I don’t see a real use for inheritance in python.

If you have a different opinion, I am very welcome to hear why I am wrong.

2 Comments

  1. Keybo
    Posted February 15, 2009 at 4:19 pm | Permalink

    I have recently had to use inheritance to deal with running equations over a data set in python. And it involved running a set of different types of equations over the same set. Therefore almost all of the functions for each of these equation classes were the same (such as extracting the data, setting the test and training sets, calculating precision and recall) of course apart from the main equation.

    This probably could have been done another way, but the way I did it was write a “generalEquation ” class. And then for each of the “other generalEquation” classes I just overwrote the equation function.

    So it went something like this:
    Class generalEquation():

    def processData():
    setVariables()
    extractData()
    normalizeData()
    runEquation()
    CalculatePrecissionRecall()

    Class EquationA(generalEquation):
    def runEquation():
    equationA()

    Class EquationB(generalEquation):
    def runEquation():
    equationB()

    EDIT Stefano: fixed typo in comment on poster’s request

  2. ilya n
    Posted June 1, 2009 at 11:28 pm | Permalink

    I like your question. Maybe you could post it verbatim on stackoverflow?