Inheritance and Classes

The concept of inheritance is something we’ve held off from mentioning until now, but it’s definitely worth understanding if you are serious about learning how to code. In effect, it’s common to find Python classes that import a ‘base class’ and then extend it with additional functionality.

The ‘Tree of Life’

Here’s a simple way to think about inheritance: think of the ‘evolutionary trees’ you might have seen charting the evolution of organisms over time. At the bottom of the tree is the single-celled animal, and at the other end are humans, whales, wildebeest, etc. We all inherit some basic functionality from that original, simple cell.

In between us and that primitive, however, are a whole series of branches: different bits of the tree evolved in different directions and developed different ‘functionality’. Some of us have bones. Some have cartilege. Some are vegetarian, and some are carnivorous. And so on.

When you get to the primates we all share certain common ‘features’ (binocular vision, grasping hands, etc.), but we are still more similar to gorillas than we are to macaques. So gorillas and humans extend the primitive ‘primate functionality’ with some bonus features (bigger brains, greater strength, etc.) that are useful, while macaques extend it with a slightly different set of features (tails, etc.).

Tree of Life

The ‘Tree of Classes’

Inheritance in code works in a similar way: all Python classes inherit their most basic functionality from a single primitive ‘object’ class that itself does very little except to provide a template for what an object should look like. As you move along the inheritance tree you will find more and more complex objects with increasingly advanced features.

I can’t find an image of Python base class inheritance, but I’ve found an equally useful example of how anything can be modelled using this ‘family tree’ approach… consider the following:

Vehicle Inheritance

If we were trying to implement a vehicle registration scheme in Python, we would want to start with the most basic category of all: vehicle. The vehicle class itself might not do much, but it gives us a template for all vehicles (e.g. it must be registered, it must have a unique license number, etc.). We then extend the functionality of this ‘base class’ with three intermediate classes: two-wheeled vehicles, cars, and trucks. These, in turn, lead to eight actual vehicle types. These might have additional functionality: a bus might need have a passenger capacity associated with it, while a convertible might need to be hard- or soft-top. All of this could be expressed in Python as:

class vehicle(object): # Inherit from base class
    def __init__(self):
        ... do something ...

class car(vehicle): # Inherit from vehicle
    def __init__(self):
        ... do other stuff ...

class sedan(car): # Inherit from car
    def __init__(self):
        ... do more stuff ...

This way, when we create a new sedan, it automatically ‘knows’ about vehicles and cars, and can make use of functions like set_unique_id(<identification>) even if that function is only specified in the base vehicle class! The thing to remember is that programmers are lazy: if they can avoid reinventing the wheel, they will. Object-Oriented Programming using inheritance is a good example of constructive laziness: it saves us having to constantly copy and paste code (for registering a new vehicle or reading in a CSV file) from one class to the next since we can just import it and extend it!

Advantages of Inheritance #1

This also means that we are less likely to make mistakes: if we want to update our vehicle registration scheme then we don’t need to update lots of functions all over the place, we just update the base class and all inheriting classes automatically gain the update because they are making use of the base class’ function.

Advantages of Inheritance #2

Inheritance also means that you can always use an instance of a ‘more evolved’ class in place of one of its ancestors: simplifying things a bit, a sedan can automatically do anything that a car can do and, by extension, anything that a vehicle can do.

Designing for Inheritance

Finally, looking back at our example above: what about unicycles? Or tracked vehicles like a tank? This is where design comes into the picture: when we’re planning out a family tree for our work we need to be careful about what goes where. And there isn’t always a single right answer: perhaps we should distinguish between pedal-powered and motor-powered (in which case unicycles, bicycles and tricycles all belong in the same family)? Or perhaps we need to distinguish between wheeled and tracked (in which case we’re missing a pair of classes [wheeled, tracked] between ‘vehicle’ and ‘two-wheel, car, truck’)? These choices are tremendously important but often very hard to get right.

OK, that’s enough programming theory, let’s see this in action…

Classes

A Basic Shape

Here is a simple demonstration of how classes work and why they’re useful in programming. Let’s see how we might create and work with a ‘shape’ class in Python:

from math import pi

class shape(object): # Inherit from base class 
    def __init__(self): 
        return 
    
    def volume(self):
        raise Exception("Unimplmented method error.")
    
    def diameter(self):
        raise Exception("Unimplmented method error.")
        
    def type(self):
        return(self.shape_type)

Notice how the def statements are themselves indented here? What we are seeing is that a class is a bundle of functions. Our shape class inherits from the object class (which gives it some basic Python skills) and then adds functions for the volume, diameter, and type. There’s also a special function called __init__ which is the one Python will call when it wants to create a new shape.

But notice also that there are some odd things about those methods (volume, diameter, and type)? volume and diameter both throw an error (aka. raise an exception) and type seems to return a value that is never set! What’s going on???

The last thing to notice is that the class methods (those functions again) all have a default parameter of self – this is a bit confusing, but it’s just a bit of ‘magic’ so that the method can access the class of which it’s a part.

More Advanced Shapes

Let’s step it up a bit and create a few more basic shapes by extending our shape base class:

So now we’ve got sphere and cube classes that no longer raise exceptions when we try to call the diameter or volume methods but return real values. Notice that we also can’t get away with creating shapes without actual measurements: the init funciton now requires an ‘edge’ or ‘radius’ value.

See if you can create a sphere of radius 10, and a cube of edge-length 10, thhen print out the diameter and volume of each.

s = sphere(10)
print(s.type())
print("\tVolume is: {0:5.2f}".format(s.volume()))
print("\tDiameter is: {0:5.2f}".format(s.diameter()))
print("")

c = cube(10)
print(c.type())
print("\tVolume is: {0:5.2f}".format(c.volume()))
print("\tDiameter is: {0:5.2f}".format(c.diameter()))
print("")
Sphere
    Volume is: 4188.79
    Diameter is: 20.00

Cube
    Volume is: 1000.00
    Diameter is: 14.14

Have a really good think about how this kind of behaviour is useful!

Test Your Understanding

This is really hard stuff and you should not panic if you are struggling to work out what is going on. However, you will find it useful to grapple with this as best you can for as long as you can stand, because this kind of thing is fundamental to good coding and, as importantly, good debugging. So… based on the above examples of classes and methods, I have two challenges for you to implement in the code below:

  1. Try implementing a regular pyramid class with a square base!
  2. Try adding an area method that returns the surface area of each shape and then add that information to the output below.

How would you test these changes?

Credits!

Contributors:

The following individuals have contributed to these teaching materials: - James Millington - Jon Reades

License

The content and structure of this teaching project itself is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 license, and the contributing source code is licensed under The MIT License.

Acknowledgements:

Supported by the Royal Geographical Society (with the Institute of British Geographers) with a Ray Y Gildea Jr Award.

Potential Dependencies:

This lesson may depend on the following libraries: None