Python Arena
Step 4 of 4

Class hierarchies and isinstance

Inheritance can go on for more than one level. A derived class can be the base class of another class, and a base class can have many derived classes. Together they form a class hierarchy, which you can draw like a family tree. Here bicycles and cars are vehicles, and an electric car is a special kind of car:

Vehicle
    Bicycle
    Car
        ElectricCar

An ElectricCar inherits everything from Car, including everything Car inherited from Vehicle:

class Vehicle:
    def __init__(self, name):
        self.name = name

    def describe(self):
        return f"{self.name} moves"


class Bicycle(Vehicle):
    def describe(self):
        return f"{self.name} is pedaled"


class Car(Vehicle):
    def honk(self):
        return f"{self.name}: beep beep"


class ElectricCar(Car):
    def __init__(self, name, battery):
        super().__init__(name)
        self.battery = battery

    def charge(self):
        self.battery = 100


spark = ElectricCar("Spark", 35)
print(spark.describe())
print(spark.honk())
spark.charge()
print(spark.battery)
Output
Spark moves
Spark: beep beep
100

To find describe, Python looks in ElectricCar, then in Car, then in Vehicle, and uses the first one it finds. super().__init__(name) in ElectricCar runs the constructor Car inherited, the one in Vehicle.

isinstance

Sometimes a program needs to know what kind of object it has. The function isinstance(value, class) tells you: it returns True if the value is an object of that class, or of a class derived from it. It fits the "is a" test: an electric car is a car, and it's a vehicle too. A list can hold objects of different classes of one hierarchy, and isinstance picks out the ones you need:

class Vehicle:
    def __init__(self, name):
        self.name = name


class Bicycle(Vehicle):
    pass


class Car(Vehicle):
    pass


class ElectricCar(Car):
    pass


spark = ElectricCar("Spark")
print(isinstance(spark, ElectricCar))
print(isinstance(spark, Car))
print(isinstance(spark, Vehicle))
print(isinstance(spark, Bicycle))

garage = [Car("Rusty"), Bicycle("Fixie"), spark, ElectricCar("Volt")]
for vehicle in garage:
    if isinstance(vehicle, Car):
        print(vehicle.name, "needs a parking space")
Output
True
True
True
False
Rusty needs a parking space
Spark needs a parking space
Volt needs a parking space

The electric cars count as cars, so they need a parking space too. The bicycle is a vehicle but not a car, so it's left out. Checking type(vehicle) == Car instead would leave out the electric cars as well: type gives only the object's own class, while isinstance follows the hierarchy.

isinstance works with Python's own types too:

In the Python interpreter
>>> isinstance(42, int)
True
>>> isinstance("42", int)
False
>>> isinstance([1, 2], list)
True

Every class you write is part of a hierarchy as well. A class with no base class in parentheses inherits Python's built-in class object. That's where the methods you never wrote come from, such as the plain __str__ that prints an object's address.

Your task

Challenge 1 of 3

What does this program print? Type each line it prints.

Challenge 2

A media library holds movies, clips and songs. Movies and clips are both videos. Write a function video_titles(items) that gets a list of these objects and returns a list of the titles of all the videos, in the order they are in the list. For example,

items = [Movie("Arrival"), Song("Yellow"), Clip("Cat on a piano"), Video("Holiday 2025")]
print(video_titles(items))

should print:

['Arrival', 'Cat on a piano', 'Holiday 2025']

Challenge 3

Build a small class hierarchy of your own on top of Character:

  • Wizard inherits Character. Its constructor takes a name, health and mana. Its method spell_cost() returns 10. Its method cast_spell() uses up spell_cost() mana and returns NAME casts a spell. If the wizard has less mana than a spell costs, nothing is used up and it returns NAME is out of mana. A wizard prints as a character does, followed by , MANA mana.
  • Archmage inherits Wizard. An archmage's spells cost only 5 mana: override spell_cost(), and nothing else.

For example,

ozma = Archmage("Ozma", 40, 12)
print(ozma.cast_spell())
print(ozma.cast_spell())
print(ozma.cast_spell())
print(ozma)
print(isinstance(ozma, Character))

should print:

Ozma casts a spell
Ozma casts a spell
Ozma is out of mana
Ozma (40 health), 2 mana
True