Please use a larger screen

This presentation is designed for desktop or projector displays.

← Back to curriculum

Python Object Oriented Programming

Inheritance

Course 3

Parent Child A Child B Grandchild

to navigate

Course Curriculum

Where we are in the journey

Recap

Classes: The Blueprint

  • A class defines attributes (color, size) and methods (make_call, take_photo)
  • It doesn't represent a tangible item until we create an Object from it
  • Creating an object = instantiation
class Smartphone:
    def __init__(self, brand, color):
        self.__brand = brand
        self.__color = color

    def make_call(self, number):
        return f"Calling {number}..."
BLUEPRINT attributes brand, color methods make_call() class Smartphone

The self Parameter

A way for an object to refer to itself

  • self is the first parameter in any method defined within a class
  • It lets the method access the instance's attributes and other methods
  • When you call person1.compare_age(person2), self = person1
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def compare_age(self, other):
        if self.age > other.age:
            print(f"{self.name} is older")

person1 = Person("Alice", 30)
person2 = Person("Bob", 25)
person1.compare_age(person2)
# Alice is older

Recap

Access Control

Public

Accessible from anywhere outside the class. No underscores.

self.engine_status = "Off"

def start_engine(self):
    self.engine_status = "On"

Protected _single

Accessible within class and subclasses. Convention, not enforced.

self._fuel_level = 50

def _check_fuel(self):
    return self._fuel_level > 0

Private __double

Accessible within class only. Name mangling prevents outside access.

self.__owner = "John"

def __display_owner(self):
    print(self.__owner)

Recap

Abstraction

Hiding complexity, exposing only what matters

  • Think of a car — you turn the key and it works. You don't need to understand the engine internals.
  • In OOP, abstraction means showing only the essential features of an object
  • The interface is simple, the implementation is hidden
  • Users interact with what an object does, not how it does it
What you see 🚗 start() accelerate() brake() Hidden complexity _ignite_spark_plugs() _inject_fuel() _engage_transmission() _calculate_torque() _monitor_temperature()

The 4 Characteristics of Behaviors

Abstraction

The most essential, impressive, stable thing of an object

Encapsulation

Behavior hides internal details

Inheritance

Behaviors are inheritable

Polymorphism

Behaviors are polymorphic

Today's focus: Inheritance — how behaviors pass from parent to child classes

Agenda for Today

Recap

  • Classes, objects & self
  • Access control & abstraction
  • Magic methods & OOP benefits

Inheritance

  • 5 types of inheritance
  • super() & constructors
  • Method overriding & overloading
  • Abstract classes & properties
  • Composition vs inheritance

Practice

  • Quiz questions
  • Live coding: Employee System

Inheritance

Real-world hierarchies map to code

  • A Vehicle can be a wheeled vehicle or a boat
  • A Car and a Bicycle are both wheeled vehicles
  • A Truck and Sports Car are both cars
  • Each level inherits from the one above and adds its own specifics
Vehicle 🚗 Wheeled Vehicle ⚙️ Boat Car 🚘 Bicycle 🚲 Truck 🚚 Sports Car 🏎️

Another Example: Mammals

  • Mammals is the base class
  • Humans, Cats, Dogs inherit from Mammals
  • Lions, Tigers, Leopards inherit from Cats
  • Each child gains parent's traits while adding its own
Mammals 🐾 Humans 👥 Cats 🐱 Dogs 🐶 Lions 🦁 Tigers 🐯 Leopards 🐆

Terminology

Base Class (Superclass)

The class from which properties and methods are inherited. Also known as a "parent" class.

Derived Class (Subclass)

The class that inherits from another class. Can add new properties or modify existing ones. Also known as a "child" class.

Inheritance

Allows creation of a new class that is a modified version of an existing class.

Animal 🐾 Parent / Superclass Dog 🐶 Child / Subclass Cat 🐱 Child / Subclass

Terminology in Code

class Animal: # Parent class
    def eat(self):
        print("This animal is eating")

class Dog(Animal): # Child class
    def bark(self):
        print("The dog barks")

class Cat(Animal): # Child class
    def meow(self):
        print("The cat meows")

rex = Dog()
rex.eat()   # This animal is eating
rex.bark()  # The dog barks
Animal eat() Base class (parent) Inheritance Dog bark() + eat() Cat meow() + eat() Derived classes (children)
class Dog(Animal) — the parentheses declare the inheritance relationship

isinstance() & issubclass()

Checking inheritance relationships at runtime

isinstance(obj, class)

Is this object an instance of this class or its parents?

class Animal: pass
class Dog(Animal): pass

rex = Dog()
print(isinstance(rex, Dog))     # True
print(isinstance(rex, Animal))  # True!

issubclass(child, parent)

Is this class a subclass of another class?

print(issubclass(Dog, Animal))  # True
print(issubclass(Animal, Dog))  # False
print(issubclass(Dog, Dog))     # True
Every class is considered a subclass of itself
1

Single Inheritance

A class inherits from only one parent

class Animal:
    def eat(self):
        print("This animal is eating")

class Dog(Animal):
    def bark(self):
        print("The dog barks")

Dog inherits from Animal, gaining eat()

Animal 🐾 eat() Dog 🐶 bark()
2

Hierarchical Inheritance

Multiple classes inherit from a single parent

class Animal:
    def eat(self):
        print("This animal is eating")

class Dog(Animal):
    def bark(self): ...

class Cat(Animal):
    def meow(self): ...
Animal 🐾 eat() Dog 🐶 bark() Cat 🐱 meow()
3

Multiple Inheritance

A class inherits from more than one base class

class Father:
    def gardening(self):
        print("Enjoys gardening")

class Mother:
    def cooking(self):
        print("Enjoys cooking")

class Child(Father, Mother):
    pass

child = Child()
child.gardening()  # From Father
child.cooking()    # From Mother
Father 👨 Mother 👩 Child 👧
Method conflicts? Python searches left-to-right: class Child(Father, Mother) checks Father first, then Mother
4

Multilevel Inheritance

An inheritance chain — class derived from a class derived from another

class Grandparent:
    def walking(self):
        print("Enjoys walking")

class Parent(Grandparent):
    def reading(self):
        print("Enjoys reading")

class Child(Parent):
    def playing(self):
        print("Enjoys playing")
Grandparent 👴 Parent 👨 Child 👧
5

Hybrid Inheritance

A combination of two or more types

  • Diamond inheritance: a derived class inherits from two classes that both inherit from the same base class
  • Python handles this with the MRO (Method Resolution Order)
Base Class Class A Class B Class C Diamond pattern

Inheritance in Practice: Vehicles

Let's build this hierarchy in code

  • Vehicle has make, model, year + display_info()
  • Car adds max_speed + show_speed()
  • Boat adds max_knots + show_knots()
  • Amphibious inherits from both Car and Boat
This example combines single, hierarchical, and multiple inheritance
Vehicle 🚗 make, model, year display_info() Car 🚘 + max_speed + show_speed() Boat + max_knots + show_knots() Amphibious Vehicle 🚤 inherits all attrs + methods

Vehicle Hierarchy: Code

class Vehicle:
    def __init__(self, make, model, year):
        self.make = make
        self.model = model
        self.year = year

    def display_info(self):
        print(f"{self.make} {self.model}, {self.year}")

class Car(Vehicle):
    def __init__(self, make, model, year, max_speed):
        super().__init__(make, model, year)
        self.max_speed = max_speed

    def show_speed(self):
        print(f"Max: {self.max_speed} mph")

class Boat(Vehicle):
    def __init__(self, make, model, year, max_knots):
        super().__init__(make, model, year)
        self.max_knots = max_knots

    def show_knots(self):
        print(f"Max: {self.max_knots} knots")

class AmphibiousVehicle(Car, Boat):
    def __init__(self, make, model, year,
                 max_speed, max_knots):
        Car.__init__(self, make, model, year, max_speed)
        Boat.__init__(self, make, model, year, max_knots)
Vehicle 🚗 make, model, year display_info() Car 🚘 + max_speed + show_speed() Boat + max_knots + show_knots() Amphibious Vehicle 🚤 inherits all attrs + methods

__init__ in Inheritance

How constructors chain through the hierarchy

The Problem

Child's __init__ overrides parent's. Parent attributes are lost!

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

class Dog(Animal):
    def __init__(self, name, breed):
        self.breed = breed  # name is LOST!

rex = Dog("Rex", "Labrador")
print(rex.breed)  # Labrador
print(rex.name)   # AttributeError!

The Solution

Call super().__init__() to chain constructors

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

class Dog(Animal):
    def __init__(self, name, breed):
        super().__init__(name)
        self.breed = breed

rex = Dog("Rex", "Labrador")
print(rex.name)   # Rex ✔
print(rex.breed)  # Labrador ✔
Rule: Always call super().__init__() when overriding the constructor

Constructor Chaining

Each level initializes its own attributes, then delegates upward

class Animal:
    def __init__(self, name):
        print("Animal init")
        self.name = name

class Dog(Animal):
    def __init__(self, name, breed):
        print("Dog init")
        super().__init__(name)
        self.breed = breed

class Puppy(Dog):
    def __init__(self, name, breed, toy):
        print("Puppy init")
        super().__init__(name, breed)
        self.toy = toy

p = Puppy("Max", "Beagle", "Ball")

Output order:

Puppy init    # 1. Puppy starts
Dog init      # 2. Delegates to Dog
Animal init   # 3. Delegates to Animal
Execution flows down (Puppy → Dog → Animal) but initialization chains upward via super()
  • p.name = "Max"
  • p.breed = "Beagle"
  • p.toy = "Ball"

Understanding Method Overriding

A subclass provides a specific implementation for a method already defined in its superclass

Customization

Subclasses tailor or modify inherited behavior without altering the superclass code

Extensibility

Subclasses can extend behavior by adding steps and calling super()

Polymorphism

A single interface can represent different underlying forms

Overriding in Action

class Animal:
    def speak(self):
        print("Animal speaks")

class Dog(Animal):
    def speak(self):
        print("Dog barks")

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

dog = Dog()
dog.speak()  # Dog barks
cat = Cat()
cat.speak()  # Cat meows
Animal 🐾 speak() → "Animal speaks" Dog 🐶 speak() → "Dog barks" Cat 🐱 speak() → "Cat meows" Same method name, different behavior

Rules for Method Overriding

1. Method Signature

Same name and parameters as the superclass method. Return type should be the same or a subtype.

2. Access Level

Cannot be more restrictive than the method in the superclass.

3. super() Invocation

The overridden method can still be accessed using super().

class Dog(Animal):
    def speak(self):
        super().speak()          # calls Animal.speak()
        print("Dog barks")       # adds Dog-specific behavior

Property Inheritance

@property decorators are inherited and can be overridden

class Animal:
    def __init__(self, name):
        self._name = name

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, value):
        if not value:
            raise ValueError("Empty!")
        self._name = value
class Dog(Animal):
    def __init__(self, name, breed):
        super().__init__(name)
        self.breed = breed

# Dog inherits the property!
rex = Dog("Rex", "Labrador")
print(rex.name)    # Rex

rex.name = "Buddy" # Setter works!
rex.name = ""       # ValueError!
Properties with validation logic carry their rules to all child classes automatically

The super() Function

Access superclass methods — ensures proper initialization

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

class Dog(Animal):
    def __init__(self, name, breed):
        super().__init__(name)
        self.breed = breed
super() can call any method from the parent class, not just the constructor.
  • Access superclass methods that have been overridden
  • Ensures the superclass is properly initialized
  • Allows subclasses to extend rather than replace behavior
  • Respects the class hierarchy and MRO

Best Practices with super()

If a subclass does not define its own constructor, the parent's constructor is called automatically.
If it does define one, you must explicitly call super().__init__() to initialize the parent.

Class vs Instance Attributes

Same class, two kinds of data

class Employee:
    raise_pct = 1.05       # class attribute

    def __init__(self, name):
        self.name = name  # instance attribute

class Developer(Employee):
    raise_pct = 1.10       # overrides class attr

e1 = Employee("Alice")
e2 = Employee("Bob")
d1 = Developer("Carol")

print(e1.name)       # Alice
print(e2.name)       # Bob
print(e1.raise_pct)  # 1.05
print(e2.raise_pct)  # 1.05 (same!)
print(d1.raise_pct)  # 1.10 (overridden)

Class attribute raise_pct

Shared by all instances. Defined outside __init__. Child classes can override it without affecting the parent.

Instance attribute name

Unique to each object. Set in __init__ via self. Every employee has their own name.

How Access Modifiers Affect Inheritance

Public

Subclasses freely inherit and access public methods and properties

self.public_var = "Public"
# accessible everywhere

Protected

Accessible within class and subclasses. Allows safe modification without exposure.

self._protected_var = "Prot"
# convention, not enforced

Private

Not directly accessible to subclasses. Name mangling prevents direct access, but parent methods using them still work.

self.__private_var = "Priv"
# raises AttributeError

Overloading vs Overriding

Method Overloading

Same name, different parameters

Within one class:

class Calculator:
    def add(self, *args):
        return sum(args)

calc.add(1, 2)      # 3
calc.add(1, 2, 3)   # 6

Across inheritance:

class Animal:
    def eat(self):
        print("Eating")

class Dog(Animal):
    def eat(self, food="meat"):
        print(f"Eating {food}")

Python simulates this with default args / *args

Method Overriding

Subclass redefines a parent method with same signature

class Animal:
    def speak(self):
        print("Animal sound")

class Dog(Animal):
    def speak(self):
        print("Dog barks")

pet = Dog()
pet.speak()  # Dog barks

Straightforward in Python

Overriding Example: Shapes

class Shape:
    def __init__(self):
        pass

    def area(self):
        return 0

class Rectangle(Shape):
    def __init__(self, width, height):
        super().__init__()
        self.__width = width
        self.__height = height

    def area(self):
        return self.__width * self.__height
import math

class Circle(Shape):
    def __init__(self, radius):
        super().__init__()
        self.__radius = radius

    def area(self):
        return math.pi * self.__radius**2
Both Rectangle and Circle override area() with their own formula

Common Mistakes

Pitfalls to watch out for

Forgetting super().__init__()

class Dog(Animal):
    def __init__(self, breed):
        # Missing super().__init__()!
        self.breed = breed

rex = Dog("Lab")
rex.name  # AttributeError!

Mutable Class Attributes

class Animal:
    tricks = []  # SHARED!

class Dog(Animal): pass
class Cat(Animal): pass

Dog().tricks.append("fetch")
print(Cat().tricks)
# ['fetch'] -- oops!

Too Deep Hierarchies

# Don't do this:
Animal
  Mammal
    Carnivore
      Canine
        DomesticDog
          Labrador
            ChocolateLab
# Keep it 2-3 levels!

Inheritance: Why Do We Care?

Quiz Time!

What does this code print?

class A:
    def greet(self):
        print("Hello from A")

class B(A):
    def greet(self):
        print("Hello from B")

class C(B):
    pass

obj = C()
obj.greet()

A) Hello from A

B) Hello from B

C) Error

D) Nothing

C has no greet(), so Python follows the MRO: C → B → finds it in B

Quiz Time!

What's the output?

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

class Car(Vehicle):
    def __init__(self, speed, brand):
        self.brand = brand

car = Car(120, "Toyota")
print(car.speed)

A) 120

B) AttributeError

C) None

D) Toyota

Car's __init__ overrides Vehicle's but never calls super().__init__(speed) — so speed is never set!

Quiz Time!

Identify the inheritance type

class Flyable:
    def fly(self): print("Flying")

class Swimmable:
    def swim(self): print("Swimming")

class Duck(Flyable, Swimmable):
    def quack(self): print("Quack!")

A) Single

B) Hierarchical

C) Multiple

D) Multilevel

Duck inherits from two unrelated parent classes — that's multiple inheritance

Hands-on

Live Coding Session

Employee Management System

What we'll build

  • Employee base class
  • Developer — single inheritance
  • Manager — method overriding
  • TechLead — multiple inheritance

Concepts covered

  • super().__init__() chaining
  • Method overriding & super()
  • isinstance() & MRO
  • Polymorphism in action

Key Takeaways

Next up: Polymorphism