Inheritance and Polymorphism

Python 3.14 · ✓ verified by execution on 2026-07-26

When building complex applications, you often find yourself creating classes that share similar characteristics. Inheritance allows a new class (the child or derived class) to inherit attributes and methods from an existing class (the parent or base class).

Inheriting Methods

To inherit from another class, place the parent class’s name inside parentheses after the new class’s name.

python
class Vehicle:
    def start(self):
        print('Starting engine')

class Car(Vehicle):
    pass

my_car = Car()
my_car.start()
Output
Starting engine

The Car class above automatically receives the start() method from Vehicle without you having to rewrite it.

Overriding and super()

Often, you want the child class to have its own specialized behavior. You can override a parent method by defining a method with the same name in the child class:

python
class Animal:
    def speak(self):
        print('Generic sound')

class Cat(Animal):
    def speak(self):
        print('Meow!')

pet = Cat()
pet.speak()
Output
Meow!

If you override a method—especially __init__—but still want the parent’s logic to execute, you use the super() function. It acts as a proxy that delegates method calls to the parent class.

python
class Employee:
    def __init__(self, name):
        self.name = name

class Manager(Employee):
    def __init__(self, name, department):
        super().__init__(name)
        self.department = department

boss = Manager('Alice', 'Sales')
print(boss.name, boss.department)
Output
Alice Sales

Warning: A very common bug occurs when a child class overrides __init__ but forgets to call super().__init__(). This causes the parent initialization to be completely bypassed, often leading to missing attributes!

Polymorphism and Duck Typing

Polymorphism means “many forms”. It allows you to write code that can operate on different types of objects, as long as they share a common interface.

Unlike languages like Java that require strict inheritance hierarchies, Python embraces Duck Typing: “If it walks like a duck and quacks like a duck, it must be a duck.”

python
class Dog:
    def speak(self):
        return 'Woof'

class Duck:
    def speak(self):
        return 'Quack'

def animal_sound(animal):
    print(animal.speak())

animal_sound(Dog())
animal_sound(Duck())
Output
Woof
Quack

Because both Dog and Duck have a speak() method, the animal_sound function works perfectly with both, even though they don’t inherit from a common base class!

Checking Types Correctly

When working with inheritance hierarchies, checking if an object is of a specific type can be tricky. You should almost always use isinstance() instead of checking type() directly.

python
class Vehicle: pass
class Car(Vehicle): pass

c = Car()
print(isinstance(c, Car))
print(isinstance(c, Vehicle))
Output
True
True

isinstance() understands that a Car is a Vehicle. Strict type() checking fails to recognize this relationship:

python
class Vehicle: pass
class Car(Vehicle): pass

c = Car()
print(type(c) == Car)
print(type(c) == Vehicle)
Output
True
False

Check yourself

If a child class does NOT define an __init__ method, what happens when it is instantiated?

Reveal answer

An empty instance is created with no attributes, bypassing the parent. — Child classes automatically inherit all methods, including __init__, from their parent class if they don't override them.

What is the primary advantage of Python's 'duck typing' approach to polymorphism?

Reveal answer

It forces all classes to inherit from a strict abstract interface. — Duck typing ('If it walks like a duck and quacks like a duck...') means Python cares about an object's behavior (its methods), not its explicit inheritance hierarchy.

Why is isinstance() generally preferred over checking type(obj) == ParentClass?

Reveal answer

type() cannot be used on custom objects. — Because subclasses should be usable wherever the parent class is expected, isinstance(obj, Parent) correctly respects the inheritance tree.

Challenges

Challenge 1 +25 XP

Fix the bug! The ElectricCar class overrides __init__ to set a battery_size, but it forgets to call the parent's initializer. Fix it using super() so that the car has both a brand and a battery_size.

python
  • Test 1 — expects "Tesla with 100kWh\n"
Need a hint? (−25% XP)

Add super().__init__(brand) before setting the battery size.

Show solution (0 XP)
class Car:
    def __init__(self, brand):
        self.brand = brand

class ElectricCar(Car):
    def __init__(self, brand, battery_size):
        super().__init__(brand)
        self.battery_size = battery_size

# Do not change the code below
my_tesla = ElectricCar('Tesla', 100)
print(f"{my_tesla.brand} with {my_tesla.battery_size}kWh")

Challenge 2 +25 XP

Create a `Shape` base class with an `area` method returning 0. Then, create a `Square` class that inherits from `Shape`. The `Square` should have an `__init__` method taking `side`, and override the `area` method to return `side * side`.

python
  • Test 1 — expects "16\n"
Need a hint? (−25% XP)

Define class Shape: and def area(self): return 0. Then class Square(Shape):.

Show solution (0 XP)
class Shape:
    def area(self):
        return 0

class Square(Shape):
    def __init__(self, side):
        self.side = side
    def area(self):
        return self.side * self.side

# Test code
s = Square(4)
print(s.area())