Object-Oriented Programming

Python Inheritance

Learn about inheritance in Python - single, multiple, and multilevel inheritance with method resolution order and super().

Python Inheritance

Inheritance allows a class (child/subclass) to inherit attributes and methods from another class (parent/superclass). It promotes code reuse and establishes a natural hierarchy between classes.


Basic Inheritance

python
# Parent (Base) class
class Animal:
    def __init__(self, name, species):
        self.name = name
        self.species = species

    def speak(self):
        return f"{self.name} makes a sound"

    def info(self):
        return f"{self.name} is a {self.species}"


# Child (Derived) class
class Dog(Animal):
    def __init__(self, name, breed):
        super().__init__(name, "Dog")  # Call parent constructor
        self.breed = breed

    def speak(self):  # Override parent method
        return f"{self.name} says Woof!"

    def fetch(self):  # New method
        return f"{self.name} fetches the ball!"


class Cat(Animal):
    def __init__(self, name, indoor=True):
        super().__init__(name, "Cat")
        self.indoor = indoor

    def speak(self):
        return f"{self.name} says Meow!"


# Usage
dog = Dog("Buddy", "Golden Retriever")
cat = Cat("Whiskers")

print(dog.speak())   # Buddy says Woof!
print(dog.info())    # Buddy is a Dog (inherited method)
print(dog.fetch())   # Buddy fetches the ball!
print(cat.speak())   # Whiskers says Meow!

The super() Function

super() calls the parent class's methods:

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

    def greet(self):
        return f"Hi, I'm {self.name}"


class Employee(Person):
    def __init__(self, name, age, company, salary):
        super().__init__(name, age)  # Initialize Person attributes
        self.company = company
        self.salary = salary

    def greet(self):
        # Call parent's greet and extend it
        base = super().greet()
        return f"{base}, I work at {self.company}"


emp = Employee("Alice", 30, "BigXStar", 95000)
print(emp.greet())  # Hi, I'm Alice, I work at BigXStar
print(emp.name)     # Alice (inherited attribute)
print(emp.salary)   # 95000

Types of Inheritance

Single Inheritance

One parent, one child:

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

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

Multiple Inheritance

One child, multiple parents:

python
class Flyable:
    def fly(self):
        return "I can fly!"

class Swimmable:
    def swim(self):
        return "I can swim!"

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

duck = Duck()
print(duck.fly())    # I can fly!
print(duck.swim())   # I can swim!
print(duck.quack())  # Quack!

Multilevel Inheritance

Chain of inheritance:

python
class Animal:
    def breathe(self):
        return "I breathe"

class Mammal(Animal):
    def feed_young(self):
        return "I feed my young with milk"

class Dog(Mammal):
    def bark(self):
        return "Woof!"

dog = Dog()
print(dog.breathe())      # I breathe (from Animal)
print(dog.feed_young())   # I feed my young with milk (from Mammal)
print(dog.bark())         # Woof! (own method)

Method Resolution Order (MRO)

When using multiple inheritance, Python follows the MRO to determine which method to call:

python
class A:
    def greet(self):
        return "Hello from A"

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

class C(A):
    def greet(self):
        return "Hello from C"

class D(B, C):
    pass

d = D()
print(d.greet())   # Hello from B (B comes before C)

# View the MRO
print(D.__mro__)
# (<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>)

# Or use mro() method
for cls in D.mro():
    print(cls.__name__)
# D → B → C → A → object

isinstance() and issubclass()

python
class Animal:
    pass

class Dog(Animal):
    pass

class Cat(Animal):
    pass

dog = Dog()

# isinstance - check if object is an instance of a class
print(isinstance(dog, Dog))     # True
print(isinstance(dog, Animal))  # True (parent counts!)
print(isinstance(dog, Cat))     # False

# issubclass - check class hierarchy
print(issubclass(Dog, Animal))   # True
print(issubclass(Cat, Animal))   # True
print(issubclass(Dog, Cat))      # False
print(issubclass(Animal, object))  # True (everything inherits object)

Mixins

Mixins are classes designed to add functionality through multiple inheritance:

python
class JsonMixin:
    """Adds JSON serialization capability."""
    def to_json(self):
        import json
        return json.dumps(self.__dict__, indent=2)

class LogMixin:
    """Adds logging capability."""
    def log(self, message):
        print(f"[{self.__class__.__name__}] {message}")

class User(JsonMixin, LogMixin):
    def __init__(self, name, email):
        self.name = name
        self.email = email

user = User("Alice", "alice@example.com")
print(user.to_json())
# {
#   "name": "Alice",
#   "email": "alice@example.com"
# }

user.log("User created")
# [User] User created

Practical Example: Shape Hierarchy

python
"""
Shape hierarchy demonstrating inheritance.
"""
import math

class Shape:
    def __init__(self, color="black"):
        self.color = color

    def area(self):
        raise NotImplementedError("Subclasses must implement area()")

    def perimeter(self):
        raise NotImplementedError("Subclasses must implement perimeter()")

    def describe(self):
        return (
            f"{self.__class__.__name__} ({self.color}): "
            f"Area={self.area():.2f}, Perimeter={self.perimeter():.2f}"
        )


class Circle(Shape):
    def __init__(self, radius, color="black"):
        super().__init__(color)
        self.radius = radius

    def area(self):
        return math.pi * self.radius ** 2

    def perimeter(self):
        return 2 * math.pi * self.radius


class Rectangle(Shape):
    def __init__(self, width, height, color="black"):
        super().__init__(color)
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

    def perimeter(self):
        return 2 * (self.width + self.height)


class Square(Rectangle):
    def __init__(self, side, color="black"):
        super().__init__(side, side, color)


# Usage
shapes = [
    Circle(5, "red"),
    Rectangle(4, 6, "blue"),
    Square(3, "green"),
]

for shape in shapes:
    print(shape.describe())
# Circle (red): Area=78.54, Perimeter=31.42
# Rectangle (blue): Area=24.00, Perimeter=20.00
# Square (green): Area=9.00, Perimeter=12.00

# Polymorphism in action
total_area = sum(s.area() for s in shapes)
print(f"\nTotal area: {total_area:.2f}")

Summary

  • Inheritance lets child classes inherit attributes and methods from parent classes
  • super() calls the parent class's methods (especially __init__)
  • Python supports single, multiple, and multilevel inheritance
  • MRO (Method Resolution Order) determines which method to call in diamond hierarchies
  • Use isinstance() to check object types and issubclass() for class relationships
  • Mixins add reusable functionality through multiple inheritance
  • Always call super().__init__() in child constructors to initialize parent attributes

Next, we'll learn about polymorphism in Python.