Introduction

Inheritance is yet another key concept in Object Oriented Programming, and it plays a vital role in building classes. It allows a class to be based off on an existing one.

When you first started writing Python classes, you were told to just put "Object" in the parenthesis of the class definition and not think too much about it. Well, now's the time to start thinking about it.

"Object" is actually the base class that all Python classes inherit from. It defines a basic set of functionality that all Python classes should have. By inheriting from it when you create a new class, you ensure that that class has that basic functionality.

In short, inheritance is a nice way of categorizing classes and making sure that you don't needlessly repeat yourself.

What Is Inheritance?

Inheritance exists in the real world too. The first couple of guides used a car as the example of a class. Well, what if you want more specific types of cars that all share those basic principles of a car? Inheritance can help.

You can start out with a basic "Car" class that has all of the properties that every car shares. These would all be very general.

After you have your "Car," you can create new classes and pass them "Car." They will all have the same properties as the base "Car" class. Then, you can add any additional properties that you want to those more specialized classes. For example, you can have "Truck," "Muscle Car," and "SUV" classes that all inherit from "Car."

When you think about it in real world terms, trucks, muscle cars, and SUVs all have the same basic properties as any other car, but they have specialized properties too.

You can also further specialize. There are tons of different types of trucks. So, you can create more specialized classes that inherit from "Truck." The will all start out with everything form "Car" and everything from "Truck."

Using Inheritance In Python

Alright, so now you can try this out with some real code. Set up a basic "Car" class that inherits from "Object." Try working with the example below.
class Car(object):
    def __init__(self, make = 'Ford', model = 'Pinto', year = '1971', mileage = 253812, color = 'orange'):
        self.__make = make
        self.__model = model
        self.__year = year
        self.__mileage = mileage
        self.__color = color

    def move_forward(self, speed):
        print("Your %s is moving forward at %s" % (self.__model, speed))

    def move_backward(self, speed):
        print("Moving backward at %s" % speed)


class MuscleCar(Car):
    __hp = 300

    def set_hp(self, hp):
        self.__hp = hp

    def get_hp(self):
        return self.__hp

    def drag_race(self, opponent):
        if (self.__hp > opponent.get_hp()):
            return "You Win!"
        else:
            return "You Lose!"

mynewcar = MuscleCar('Ford', 'Mustang', '2016', 3000, 'red')
mynewcar.set_hp(687)
opponent = MuscleCar('Ford', 'Mustang', '2014', 6400, 'green')
opponent.set_hp(465)

mynewcar.move_forward('25mph')
print(mynewcar.drag_race(opponent))
Notice that the MuscleCar objects were able to use the constructor and the move_forward method from the "Car" class even though the class they were instantiated from doesn't explicitly have them.

Overriding

Just because a class inherits from another one, you're not stuck with all of the functionality of the parent class. You can overwrite parts of the parent class within child classes. The changes applied to the child class will not apply to the original parent class, so you don't have to worry about messing up any other classes.

In the example above, the "MuscleCar" just had a variable, __hp just floating there with no way to set it on instantiation. Check out the same example, but with the constructor overridden.
class Car(object):
    def __init__(self, make = 'Ford', model = 'Pinto', year = '1971', mileage = 253812, color = 'orange'):
        self.__make = make
        self.__model = model
        self.__year = year
        self.__mileage = mileage
        self.__color = color

    def move_forward(self, speed):
        print("Your %s is moving forward at %s" % (self.__model, speed))

    def move_backward(self, speed):
        print("Moving backward at %s" % speed)


class MuscleCar(Car):
    def __init__(self, make = 'Ford', model = 'Mustang', year = '1965', mileage = 54032, color = 'blue', hp = 325):
        self.__make = make
        self.__model = model
        self.__year = year
        self.__mileage = mileage
        self.__color = color
		self.__hp = hp

    def set_hp(self, hp):
        self.__hp = hp

    def get_hp(self):
        return self.__hp

    def drag_race(self, opponent):
        if (self.__hp > opponent.get_hp()):
            return "You Win!"
        else:
            return "You Lose!"

mynewcar = MuscleCar('Ford', 'Mustang', '2016', 3000, 'red', 687)
opponent = MuscleCar()


mynewcar.move_forward('25mph')
print(mynewcar.drag_race(opponent))
There are two things to notice. First, __hp has become self.__hp and is incorporated into the constructor. Because of this, setting it is much easier. Second, the default values for a new "MuscleCar" have been changed. A Pinto isn't a very good default muscle car, is it?

You can do this with any variable or method in a subclass or child class. It adds an additional degree of flexibility and prevents you from being locked into the functionality of the parent or super class.

The Super Method

Sometimes, you need to access the methods found in the parent class from within the child class. Take the previous example which overrides that constructor. A lot of that code is redundant. Using super() to call the constructor from the "Car" class eliminates that redundancy and makes for a more streamlined class.

super() can also just be used to access regular methods for use in subclass methods. The example below used super() both ways.
class Car(object):
    def __init__(self, make = 'Ford', model = 'Pinto', year = '1971', mileage = 253812, color = 'orange'):
        self.__make = make
        self.__model = model
        self.__year = year
        self.__mileage = mileage
        self.__color = color

    def set_make(self, make):
        self.__make = make

    def get_make(self):
        return self.__make

    def set_model(self, model):
        self.__model = model

    def get_model(self):
        return self.__model

    def set_year(self, year):
        self.__year = year

    def get_year(self):
        return self.__year

    def set_mileage(self, mileage):
        self.__mileage = mileage

    def get_mileage(self):
        return self.__mileage

    def set_color(self, color):
        self.__color = color
def get_color(self):
        return self.__color

    def move_forward(self, speed):
        print("Your %s is moving forward at %s" % (self.__model, speed))

    def move_backward(self, speed):
        print("Moving backward at %s" % speed)


class MuscleCar(Car):
    def __init__(self, make = 'Ford', model = 'Mustang', year      = '1965', mileage = 54032, color = 'blue', hp = 325):
        super().__init__(make, model, year, mileage, color)
        self.__hp = hp


    def set_hp(self, hp):
        self.__hp = hp

    def get_hp(self):
        return self.__hp

    def drag_race(self, opponent):
        if (self.__hp > opponent.get_hp()):
            return "You Win!"
        else:
            return "You Lose!"

    def trade_up(self, year, color):
        super().set_year(year)
        super().set_color(color)
        super().set_mileage(0)

mynewcar = MuscleCar('Ford', 'Mustang', '2016', 3000, 'red', 687)
opponent = MuscleCar()

mynewcar.move_forward('25mph')
print(mynewcar.drag_race(opponent))

mynewcar.trade_up('2017', 'black')
print("My new %s muscle car is %s and has %d miles" % (mynewcar.get_year(), mynewcar.get_color(), mynewcar.get_mileage()))

Look at the way the trade_up method makes use of super() to access and call those setter methods from the parent class.

Closing Thoughts

Inheritance allows you to use classes as templates for more specialized classes. You can build out classes in such a structure that the begin to resemble a family tree, with the "older" and more general members passing on traits to the "younger" more specialized members.

Much of Object Oriented Programming is reducing the amount of code that is written and preventing as much code as possible from being rewritten. Inheritance serves a big role in making this possible.

Exercises

  1. Create a basic class that inherits from the "Car" class.
  2. Instantiate your new class and call one of the methods from "Car."
  3. Create a new methods in your child class.
  4. Call your new method.
  5. Use super() to add variables to your child class's constructor.
  6. Create a method using super() to access the parent class's methods.
  7. Call your new method that uses super().