Skip to content
intermediate Phase 2 · Python OOP

Inheritance & Polymorphism

Implement class hierarchies, method overriding, and polymorphic behavior.

1h 15m
0 problems
Topic Progress 0%

Single Inheritance

Basic Inheritance

class Animal:
    def __init__(self, name):
        self.name = name
    
    def speak(self):
        raise NotImplementedError
    
    def __str__(self):
        return f'{self.name} the {self.__class__.__name__}'

class Dog(Animal):
    def speak(self):
        return f'{self.name} says Woof!'
    
    def fetch(self, item):
        return f'{self.name} fetches the {item}'

class Cat(Animal):
    def speak(self):
        return f'{self.name} says Meow!'

dog = Dog('Buddy')
cat = Cat('Whiskers')

print(dog)  # Buddy the Dog
print(dog.speak())  # Buddy says Woof!
print(dog.fetch('ball'))  # Buddy fetches the ball
print(cat.speak())  # Whiskers says Meow!

Using super()

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def __str__(self):
        return f'{self.name}, {self.age} years old'

class Student(Person):
    def __init__(self, name, age, major):
        super().__init__(name, age)  # Call parent __init__
        self.major = major
    
    def __str__(self):
        return f'{super().__str__()}, studying {self.major}'

student = Student('Alice', 20, 'Computer Science')
print(student)  # Alice, 20 years old, studying Computer Science

isinstance() and issubclass()

dog = Dog('Buddy')

isinstance(dog, Dog)    # True
isinstance(dog, Animal) # True
isinstance(dog, Cat)    # False

issubclass(Dog, Animal)   # True
issubclass(Animal, Dog)   # False
issubclass(Dog, object)   # True (everything inherits from object)

Multiple Inheritance

Multiple Inheritance

class Flyer:
    def fly(self):
        return f'{self.__class__.__name__} is flying'

class Swimmer:
    def swim(self):
        return f'{self.__class__.__name__} is swimming'

class Duck(Animal, Flyer, Swimmer):
    def speak(self):
        return f'{self.name} says Quack!'

duck = Duck('Donald')
print(duck.speak())  # Donald says Quack!
print(duck.fly())    # Duck is flying
print(duck.swim())   # Duck is swimming

Method Resolution Order (MRO)

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

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

# Or using method
print(D.mro())

super() with Multiple Inheritance

class Base:
    def __init__(self):
        print('Base.__init__')

class Left(Base):
    def __init__(self):
        super().__init__()
        print('Left.__init__')

class Right(Base):
    def __init__(self):
        super().__init__()
        print('Right.__init__')

class Child(Left, Right):
    def __init__(self):
        super().__init__()
        print('Child.__init__')

Child()
# Output:
# Base.__init__
# Right.__init__
# Left.__init__
# Child.__init__

Common Pitfalls

# ❌ The Diamond Problem (Python handles it with MRO)
class A:
    pass
class B(A):
    pass
class C(A):
    pass
class D(B, C):  # Both B and C inherit from A
    pass

# ✅ Python uses C3 linearization (MRO)
print(D.__mro__)  # D -> B -> C -> A -> object

Polymorphism

Duck Typing

# "If it walks like a duck and quacks like a duck, it's a duck"

def make_it_speak(animal):
    print(animal.speak())  # No type checking needed

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

class Cat:
    def speak(self):
        return 'Meow!'

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

# All work with the same function
for animal in [Dog(), Cat(), Duck()]:
    make_it_speak(animal)

Abstract Base Classes

from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):
        pass
    
    @abstractmethod
    def perimeter(self):
        pass
    
    def description(self):
        return f'{self.__class__.__name__} with area {self.area():.2f}'

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius
    
    def area(self):
        import math
        return math.pi * self.radius ** 2
    
    def perimeter(self):
        import math
        return 2 * math.pi * self.radius

class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height
    
    def area(self):
        return self.width * self.height
    
    def perimeter(self):
        return 2 * (self.width + self.height)

# shape = Shape()  # ❌ TypeError: Can't instantiate abstract class
circle = Circle(5)  # ✅
rect = Rectangle(4, 6)  # ✅

shapes = [circle, rect]
for shape in shapes:
    print(shape.description())

Polymorphism in Practice

def total_area(shapes):
    return sum(shape.area() for shape in shapes)

def print_shapes(shapes):
    for shape in shapes:
        print(f'{shape.__class__.__name__}: {shape.area():.2f}')

shapes = [Circle(5), Rectangle(4, 6), Circle(3)]
print(f'Total area: {total_area(shapes):.2f}')
print_shapes(shapes)

Polymorphism Benefits

  1. Flexibility: Code works with any type that implements the interface
  2. Extensibility: Add new types without modifying existing code
  3. Testability: Easy to mock and test
  4. Loose coupling: Components don't depend on concrete types