What’s the point of inheritance in Python?
Asked Answered
F

11

86

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. But 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 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...

What I’d like to point out is that I haven’t found a single reason yet to use inheritance in Python (apart of frameworks and trees of exceptions, but I guess that alternative strategies exist). you don’t need to implement a base-derived hierarchy to perform polymorphically. If you want to use inheritance to reuse implementation, you can accomplish the same through containment and delegation, 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.

So, in the end, the question stands: what's the point of inheritance in Python?

Edit: thanks for the very interesting answers. Indeed you can use it for code reuse, but I am always careful when reusing implementation. In general, I tend to do very shallow inheritance trees or no tree at all, and if a functionality is common I refactor it out as a common module routine and then call it from each object. I do see the advantage of having one single point of change (eg. instead of adding to Dog, Cat, Moose and so on, I just add to Animal, which is the basic advantage of inheritance), but you can achieve the same with a delegation chain (eg. a la JavaScript). I'm not claiming it's better though, just another way.

I also found a similar post on this regard.

Ferrochromium answered 19/6, 2009 at 23:28 Comment(7)
-1: "you can achieve the same with a delegation chain". True, but far more painful than inheritance. You can achieve the same without using any class definitions at all, just lots of complicated pure functions. You can achieve the same thing a dozen ways, all less simple than inheritance.Cordula
indeed I said "I'm not claiming it's better ;)"Ferrochromium
"I haven’t found a single reason yet to use inheritance in python"... sure sounds like "my solution is better".Cordula
Sorry if it gave you this impression. My post was aimed at getting positive feedback for real case stories of usage of inheritance in python which, as of today, I was not able to found (mainly because in all my python programming, I faced cases where this was not needed, and when I did, it was the situation I explained above).Ferrochromium
Real-world taxonomies are seldom good a basis for examples of object-orientation.Gamp
5 years down the road, I think I probably realized the concept of generic programmingFerrochromium
What type is "a" in the context of makespeak() method?Superdominant
B
81

You are referring to the run-time duck-typing as "overriding" inheritance, however I believe inheritance has its own merits as a design and implementation approach, being an integral part of object oriented design. In my humble opinion, the question of whether you can achieve something otherwise is not very relevant, because actually you could code Python without classes, functions and more, but the question is how well-designed, robust and readable your code will be.

I can give two examples for where inheritance is the right approach in my opinion, I'm sure there are more.

First, if you code wisely, your makeSpeak function may want to validate that its input is indeed an Animal, and not only that "it can speak", in which case the most elegant method would be to use inheritance. Again, you can do it in other ways, but that's the beauty of object oriented design with inheritance - your code will "really" check whether the input is an "animal".

Second, and clearly more straightforward, is Encapsulation - another integral part of object oriented design. This becomes relevant when the ancestor has data members and/or non-abstract methods. Take the following silly example, in which the ancestor has a function (speak_twice) that invokes a then-abstract function:

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

    def speak_twice(self):
        self.speak()
        self.speak()

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

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

Assuming "speak_twice" is an important feature, you don't want to code it in both Dog and Cat, and I'm sure you can extrapolate this example. Sure, you could implement a Python stand-alone function that will accept some duck-typed object, check whether it has a speak function and invoke it twice, but that's both non-elegant and misses point number 1 (validate it's an Animal). Even worse, and to strengthen the Encapsulation example, what if a member function in the descendant class wanted to use "speak_twice"?

It gets even clearer if the ancestor class has a data member, for example "number_of_legs" that is used by non-abstract methods in the ancestor like "print_number_of_legs", but is initiated in the descendant class' constructor (e.g. Dog would initialize it with 4 whereas Snake would initialize it with 0).

Again, I'm sure there are endless more examples, but basically every (large enough) software that is based on solid object oriented design will require inheritance.

Brendin answered 19/6, 2009 at 23:35 Comment(8)
For the first case, it would mean that you are checking types instead of behavior, which is kind of unpythonic. For the second case, I agree, and you are basically doing the "framework" approach. You are recycling the implementation of speak_twice, not only the interface, but for overriding, you can live without inheritance when you consider python.Ferrochromium
You can live without many things, like classes and functions, but the question is what makes code great. I think inheritance does.Brendin
@Stefano Borini - It sounds like you're taking a very "rules-based" approach. The old cliche is true though: they were made to be broken. :-)Cuckooflower
@Jason Baker - I tend to like rules because they report wisdom gained from experience (eg. mistakes), but I don't like creativity to be hindered by them. So I agree with your statement.Ferrochromium
I don't find this example so clear - animals, cars and shape examples really suck for those dicussions :) The only thing which matters IMHO is whether you want to inherit implementation or not. If so, rules in python are really similar to java/C++; the difference is mostly for inheritance for interfaces. In that case, duck-typing is often the solution - much more so than inheritance.Summerwood
@Rax : I totally agree. Python has this clear advantage: easy, simple code. I would not pollute it for anything in the world.Ferrochromium
Nice explanation.I could figure out why the OP's code wasn't elegant in a snapChorea
I kinda disagree that you should use inheritance to get more attributes or functionality into derived. As the original author mentioned, shallow inheritance and utility classes/functions instead of having them in the base class are much better on the long term w.r.t maintenance. Sure, for a quick and dirty it is always easy to take an existing class, subclass it and add few more functionality, but it gets messy and it's wrong. I've seem enough systems where the hierarchy was deep and it was impossible to follow. Even saw it in Android code where dup member was introduced in derived.Giesser
K
13

Inheritance in Python is all about code reuse. Factorize common functionality into a base class, and implement different functionality in the derived classes.

Korey answered 20/6, 2009 at 4:51 Comment(0)
C
12

Inheritance in Python is more of a convenience than anything else. I find that it's best used to provide a class with "default behavior."

Indeed, there is a significant community of Python devs who argue against using inheritance at all. Whatever you do, don't just don't overdo it. Having an overly complicated class hierarchy is a sure way to get labeled a "Java programmer", and you just can't have that. :-)

Cuckooflower answered 20/6, 2009 at 3:44 Comment(0)
G
8

I think the point of inheritance in Python is not to make the code compile, it is for the real reason of inheritance which is extending the class into another child class, and to override the logic in the base class. However the duck typing in Python makes the "interface" concept useless, because you can just check if the method exist before invokation with no need to use an interface to limit the class structure.

Gutow answered 19/6, 2009 at 23:34 Comment(3)
Selective overriding is the reason for inheritance. If you're going to override everything, that's a weird special case.Cordula
Who would override everything? you can think of python like all methods are public and virtualGutow
@bashmohandes: I would never override everything. But the question shows a degenerate case where everything gets overridden; this weird special case is the basis for the question. Since it never happens in normal OO design, the question is kind of pointless.Cordula
S
7

I think that it is very difficult to give a meaningful, concrete answer with such abstract examples...

To simplify, there are two types of inheritance: interface and implementation. If you need to inherit the implementation, then python is not so different than statically typed OO languages like C++.

Inheritance of interface is where there is a big difference, with fundamental consequences for the design of your software in my experience. Languages like Python does not force you to use inheritance in that case, and avoiding inheritance is a good point in most cases, because it is very hard to fix a wrong design choice there later. That's a well known point raised in any good OOP book.

There are cases where using inheritance for interfaces is advisable in Python, for example for plug-ins, etc... For those cases, Python 2.5 and below lacks a "built-in" elegant approach, and several big frameworks designed their own solutions (zope, trac, twister). Python 2.6 and above has ABC classes to solve this.

Summerwood answered 20/6, 2009 at 3:31 Comment(0)
O
6

It's not inheritance that duck-typing makes pointless, it's interfaces — like the one you chose in creating an all abstract animal class.

If you had used an animal class that introduce some real behavior for its descendants to make use of, then dog and cat classes that introduced some additional behavior there would be a reason for both classes. It's only in the case of the ancestor class contributing no actual code to the descendant classes that your argument is correct.

Because Python can directly know the capabilities of any object, and because those capabilities are mutable beyond the class definition, the idea of using a pure abstract interface to "tell" the program what methods can be called is somewhat pointless. But that's not the sole, or even the main, point of inheritance.

Odd answered 15/10, 2009 at 22:28 Comment(0)
O
5

In C++/Java/etc, polymorphism is caused by inheritance. Abandon that misbegotten belief, and dynamic languages open up to you.

Essentially, in Python there is no interface so much as "the understanding that certain methods are callable". Pretty hand-wavy and academic-sounding, no? It means that because you call "speak" you clearly expect that the object should have a "speak" method. Simple, huh? This is very Liskov-ian in that the users of a class define its interface, a good design concept that leads you into healthier TDD.

So what is left is, as another poster politely managed to avoid saying, a code sharing trick. You could write the same behavior into each "child" class, but that would be redundant. Easier to inherit or mix-in functionality that is invariant across the inheritance hierarchy. Smaller, DRY-er code is better in general.

Orthopterous answered 20/6, 2009 at 1:1 Comment(0)
M
3

I don't see much point in inheritance.

Every time I have ever used inheritance in real systems, I got burned because it led to a tangled web of dependencies, or I simply realised in time that I would be a lot better off without it. Now, I avoid it as much as possible. I simply never have a use for it.

class Repeat:
    "Send a message more than once"
    def __init__(repeat, times, do):
        repeat.times = times
        repeat.do = do

    def __call__(repeat):
        for i in xrange(repeat.times):
             repeat.do()

class Speak:
    def __init__(speak, animal):
        """
        Check that the animal can speak.

        If not we can do something about it (e.g. ignore it).
        """
        speak.__call__ = animal.speak

    def twice(speak):
        Repeat(2, speak)()

class Dog:
     def speak(dog):
         print "Woof"

class Cat:
     def speak(cat):
         print "Meow"

>>> felix = Cat()
>>> Speak(felix)()
Meow

>>> fido = Dog()
>>> speak = Speak(fido)
>>> speak()
Woof

>>> speak.twice()
Woof

>>> speak_twice = Repeat(2, Speak(felix))
>>> speak_twice()
Meow
Meow

James Gosling was once asked at a press conference a question along the lines: "If you could go back and do Java differently, what would you leave out?". His response was "Classes", to which there was laughter. However, he was serious and explained that really, it was not classes that were the problem but inheritance.

I kind of view it like a drug dependency - it gives you a quick fix that feels good, but in the end, it messes you up. By that I mean that it is a convenient way to reuse code, but it forces an unhealthy coupling between child and parent class. Changes to the parent may break the child. The child is dependant on the parent for certain functionality and cannot alter that functionality. Therefore the functionality provided by the child is also tied to the parent - you can only have both.

Better is to provide one single client facing class for an interface which implements the interface, using the functionality of other objects which are composed at construction time. Doing this via properly designed interfaces, all coupling can be eliminated and we provide a highly composable API (This is nothing new - most programmers already do this, just not enough). Note that the implementing class must not simply expose functionality, otherwise the client should just use the composed classes directly - it must do something new by combining that functionality.

There is the argument from the inheritance camp that pure delegation implementations suffer because they require lots of 'glue' methods which simply pass along values through a delegation 'chain'. However, this is simply reinventing an inheritance-like design using delegation. Programmers with too many years of exposure to inheritance-based designs are particularly vulnerable to falling into this trap, as, without realising it, they will think of how they would implement something using inheritance and then convert that to delegation.

Proper separation of concerns like the above code doesn't require glue methods, as each step is actually adding value, so they are not really 'glue' methods at all (if they don't add value, the design is flawed).

It boils down to this:

  • For reusable code, each class should do only one thing (and do it well).

  • Inheritance creates classes that do more than one thing, because they are mixed up with parent classes.

  • Therefore, using inheritance makes classes that are hard to reuse.

Mcferren answered 1/7, 2009 at 20:55 Comment(0)
R
1

You can get around inheritance in Python and pretty much any other language. It's all about code reuse and code simplification though.

Just a semantic trick, but after building your classes and base classes, you don't even have to know what's possible with your object to see if you can do it.

Say you have d which is a Dog that subclassed Animal.

command = raw_input("What do you want the dog to do?")
if command in dir(d): getattr(d,command)()

If whatever the user typed in is available, the code will run the proper method.

Using this you can create whatever combination of Mammal/Reptile/Bird hybrid monstrosity you want, and now you can make it say 'Bark!' while flying and sticking out its forked tongue and it will handle it properly! Have fun with it!

Retrograde answered 24/6, 2009 at 23:40 Comment(0)
S
1

Another small point is that op's 3'rd example, you can't call isinstance(). For example passing your 3'rd example to another object that takes and "Animal" type an calls speak on it. If you do it don't you would have to check for dog type, cat type, and so on. Not sure if instance checking is really "Pythonic", because of late binding. But then you would have to implement some way that the AnimalControl doesn't try to throw Cheeseburger types in the truck, becuase Cheeseburgers don't speak.

class AnimalControl(object):
    def __init__(self):
        self._animalsInTruck=[]

    def catachAnimal(self,animal):
        if isinstance(animal,Animal):
            animal.speak()  #It's upset so it speak's/maybe it should be makesNoise
            if not self._animalsInTruck.count <=10:
                self._animalsInTruck.append(animal) #It's then put in the truck.
            else:
                #make note of location, catch you later...
        else:
            return animal #It's not an Animal() type / maybe return False/0/"message"
Stanza answered 11/7, 2012 at 14:14 Comment(0)
B
0

Classes in Python are basically just ways of grouping a bunch of functions and data.. They are different to classes in C++ and such..

I've mostly seen inheritance used for overriding methods of the super-class. For example, perhaps a more Python'ish use of inheritance would be..

from world.animals import Dog

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

Of course cats aren't a type of dog, but I have this (third party) Dog class which works perfectly, except the speak method which I want to override - this saves re-implementing the entire class, just so it meows. Again, while Cat isn't a type of Dog, but a cat does inherit a lot of attributes..

A much better (practical) example of overriding a method or attribute is how you change the user-agent for urllib. You basically subclass urllib.FancyURLopener and change the version attribute (from the documentation):

import urllib

class AppURLopener(urllib.FancyURLopener):
    version = "App/1.7"

urllib._urlopener = AppURLopener()

Another manner exceptions are used is for Exceptions, when inheritance is used in a more "proper" way:

class AnimalError(Exception):
    pass

class AnimalBrokenLegError(AnimalError):
    pass

class AnimalSickError(AnimalError):
    pass

..you can then catch AnimalError to catch all exceptions which inherit from it, or a specific one like AnimalBrokenLegError

Bibeau answered 19/6, 2009 at 23:52 Comment(3)
I'm… a bit confused by your first example. Last I checked, cats aren't a kind of dog, so I'm not sure what relationship you're trying to demonstrate. :-)Semanteme
You are messing with the Liskov principle: the Cat IS NOT a Dog. It may be OK to use in this case, but what if the Dog class changes and get, for example, a "Lead" field, which is senseless for cats?Martinic
Well if there is no Animal base-class, your alternative is to reimpliment the whole thing.. I'm not saying it's the best practice (if there's an Animal base-class, use it), but it works and is used commonly (it's the recommended way of changing the user-agent of urllib, as per the example I added)Bibeau

© 2022 - 2024 — McMap. All rights reserved.