Skip to content
Python by Example

Object-Oriented Programming

Inheritance in Python

08_oop/03_inheritance.py

This file demonstrates inheritance in Python, a fundamental OOP concept that allows classes to inherit attributes and methods from other classes. This promotes code reuse and establishes relationships between classes.

"""
================================================================================
File: 03_inheritance.py
Topic: Inheritance in Python
================================================================================
 
This file demonstrates inheritance in Python, a fundamental OOP concept that
allows classes to inherit attributes and methods from other classes. This
promotes code reuse and establishes relationships between classes.
 
Key Concepts:
- Single inheritance
- Method overriding
- super() function
- Multiple inheritance
- Method Resolution Order (MRO)
- Abstract base classes
 
================================================================================
"""
 
# -----------------------------------------------------------------------------
# 1. Basic Inheritance
# -----------------------------------------------------------------------------
# Child class inherits from parent class
 
print("--- Basic Inheritance ---")
 
class Animal:
    """Base class for all animals."""
    
    def __init__(self, name):
        """Initialize animal with a name."""
        self.name = name
    
    def speak(self):
        """Make a generic sound."""
        return "Some generic sound"
    
    def describe(self):
        """Describe the animal."""
        return f"I am {self.name}"
 
class Dog(Animal):
    """Dog class inheriting from Animal."""
    pass  # Inherits everything from Animal
 
# Dog has all Animal's methods
buddy = Dog("Buddy")
print(buddy.describe())
print(f"Says: {buddy.speak()}")
print(f"Dog is instance of Animal: {isinstance(buddy, Animal)}")
 
# -----------------------------------------------------------------------------
# 2. Method Overriding
# -----------------------------------------------------------------------------
# Child class can override parent methods
 
print("\n--- Method Overriding ---")
 
class Cat(Animal):
    """Cat class with overridden method."""
    
    def speak(self):
        """Cats meow instead of generic sound."""
        return "Meow!"
 
class Cow(Animal):
    """Cow class with overridden method."""
    
    def speak(self):
        """Cows moo."""
        return "Moo!"
 
# Each animal speaks differently
animals = [Dog("Rex"), Cat("Whiskers"), Cow("Bessie")]
 
print("Each animal speaks:")
for animal in animals:
    print(f"  {animal.name}: {animal.speak()}")
 
# -----------------------------------------------------------------------------
# 3. Extending Parent Class with super()
# -----------------------------------------------------------------------------
# Use super() to call parent methods
 
print("\n--- Using super() ---")
 
class Bird(Animal):
    """Bird class extending Animal."""
    
    def __init__(self, name, can_fly=True):
        """Initialize bird with flying ability."""
        super().__init__(name)  # Call parent's __init__
        self.can_fly = can_fly
    
    def speak(self):
        """Birds chirp."""
        return "Chirp!"
    
    def describe(self):
        """Extend parent's describe method."""
        base = super().describe()  # Get parent's description
        fly_status = "can fly" if self.can_fly else "cannot fly"
        return f"{base} and I {fly_status}"
 
sparrow = Bird("Sparrow", can_fly=True)
penguin = Bird("Penny", can_fly=False)
 
print(sparrow.describe())
print(penguin.describe())
 
# -----------------------------------------------------------------------------
# 4. Inheritance Chain
# -----------------------------------------------------------------------------
# Classes can inherit from other child classes
 
print("\n--- Inheritance Chain ---")
 
class Vehicle:
    """Base vehicle class."""
    
    def __init__(self, brand):
        self.brand = brand
    
    def start(self):
        return "Vehicle starting..."
 
class Car(Vehicle):
    """Car inherits from Vehicle."""
    
    def __init__(self, brand, model):
        super().__init__(brand)
        self.model = model
    
    def start(self):
        return f"{self.brand} {self.model} engine starting..."
 
class ElectricCar(Car):
    """ElectricCar inherits from Car."""
    
    def __init__(self, brand, model, battery_kw):
        super().__init__(brand, model)
        self.battery_kw = battery_kw
    
    def start(self):
        return f"{self.brand} {self.model} powering up silently... (Battery: {self.battery_kw}kW)"
    
    def charge(self):
        return f"Charging {self.battery_kw}kW battery..."
 
# Create instances
regular_car = Car("Toyota", "Camry")
electric_car = ElectricCar("Tesla", "Model 3", 75)
 
print(regular_car.start())
print(electric_car.start())
print(electric_car.charge())
 
# Check inheritance
print(f"\nElectricCar is Car: {isinstance(electric_car, Car)}")
print(f"ElectricCar is Vehicle: {isinstance(electric_car, Vehicle)}")
 
# -----------------------------------------------------------------------------
# 5. Multiple Inheritance
# -----------------------------------------------------------------------------
# A class can inherit from multiple parent classes
 
print("\n--- Multiple Inheritance ---")
 
class Flyable:
    """Mixin class for flying ability."""
    
    def fly(self):
        return f"{self.name} is flying!"
    
    def land(self):
        return f"{self.name} has landed."
 
class Swimmable:
    """Mixin class for swimming ability."""
    
    def swim(self):
        return f"{self.name} is swimming!"
    
    def dive(self):
        return f"{self.name} dives underwater."
 
class Duck(Animal, Flyable, Swimmable):
    """Duck can do everything!"""
    
    def speak(self):
        return "Quack!"
 
# Duck has methods from all parent classes
duck = Duck("Donald")
print(f"{duck.name} says: {duck.speak()}")
print(duck.fly())
print(duck.swim())
print(duck.describe())
 
# -----------------------------------------------------------------------------
# 6. Method Resolution Order (MRO)
# -----------------------------------------------------------------------------
# Python determines method lookup order
 
print("\n--- Method Resolution Order (MRO) ---")
 
class A:
    def method(self):
        return "A"
 
class B(A):
    def method(self):
        return "B"
 
class C(A):
    def method(self):
        return "C"
 
class D(B, C):
    pass
 
class E(B, C):
    def method(self):
        return "E"
 
# Check MRO
print("MRO for class D:")
for cls in D.__mro__:
    print(f"  {cls.__name__}")
 
d = D()
e = E()
print(f"\nd.method(): {d.method()}")  # Returns "B" (first in MRO after D)
print(f"e.method(): {e.method()}")    # Returns "E" (overridden)
 
# -----------------------------------------------------------------------------
# 7. Calling Methods from Specific Parent
# -----------------------------------------------------------------------------
 
print("\n--- Calling Specific Parent Methods ---")
 
class Parent1:
    def greet(self):
        return "Hello from Parent1"
 
class Parent2:
    def greet(self):
        return "Hello from Parent2"
 
class Child(Parent1, Parent2):
    def greet(self):
        return "Hello from Child"
    
    def greet_all(self):
        """Call all parent greet methods."""
        return [
            f"Child: {self.greet()}",
            f"Parent1: {Parent1.greet(self)}",
            f"Parent2: {Parent2.greet(self)}"
        ]
 
child = Child()
print("Calling all greet methods:")
for greeting in child.greet_all():
    print(f"  {greeting}")
 
# -----------------------------------------------------------------------------
# 8. Abstract Base Classes
# -----------------------------------------------------------------------------
# Define interfaces that must be implemented
 
print("\n--- Abstract Base Classes ---")
 
from abc import ABC, abstractmethod
 
class Shape(ABC):
    """Abstract base class for shapes."""
    
    @abstractmethod
    def area(self):
        """Calculate area - must be implemented."""
        pass
    
    @abstractmethod
    def perimeter(self):
        """Calculate perimeter - must be implemented."""
        pass
    
    def describe(self):
        """Non-abstract method - can be used as-is."""
        return f"A shape with area {self.area():.2f}"
 
class Rectangle(Shape):
    """Concrete rectangle class."""
    
    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)
 
class Circle(Shape):
    """Concrete circle class."""
    
    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
 
# Cannot instantiate abstract class
# shape = Shape()  # TypeError!
 
# Concrete classes work fine
rect = Rectangle(5, 3)
circle = Circle(4)
 
print(f"Rectangle: area={rect.area()}, perimeter={rect.perimeter()}")
print(f"Circle: area={circle.area():.2f}, perimeter={circle.perimeter():.2f}")
print(rect.describe())
 
# -----------------------------------------------------------------------------
# 9. Using isinstance() and issubclass()
# -----------------------------------------------------------------------------
 
print("\n--- Type Checking ---")
 
class Mammal(Animal):
    pass
 
class Reptile(Animal):
    pass
 
class DogV2(Mammal):
    def speak(self):
        return "Woof!"
 
class Snake(Reptile):
    def speak(self):
        return "Hiss!"
 
dog = DogV2("Rex")
snake = Snake("Slinky")
 
# isinstance() checks if object is instance of class
print("isinstance checks:")
print(f"  dog isinstance of DogV2: {isinstance(dog, DogV2)}")
print(f"  dog isinstance of Mammal: {isinstance(dog, Mammal)}")
print(f"  dog isinstance of Animal: {isinstance(dog, Animal)}")
print(f"  dog isinstance of Reptile: {isinstance(dog, Reptile)}")
 
# issubclass() checks class relationships
print("\nissubclass checks:")
print(f"  DogV2 issubclass of Mammal: {issubclass(DogV2, Mammal)}")
print(f"  DogV2 issubclass of Animal: {issubclass(DogV2, Animal)}")
print(f"  DogV2 issubclass of Reptile: {issubclass(DogV2, Reptile)}")
 
# -----------------------------------------------------------------------------
# 10. Practical Example: Employee Hierarchy
# -----------------------------------------------------------------------------
 
print("\n--- Practical Example: Employee Hierarchy ---")
 
class Employee:
    """Base employee class."""
    
    def __init__(self, name, employee_id, salary):
        self.name = name
        self.employee_id = employee_id
        self._salary = salary
    
    @property
    def salary(self):
        return self._salary
    
    def get_annual_salary(self):
        return self._salary * 12
    
    def __str__(self):
        return f"{self.name} (ID: {self.employee_id})"
 
class Manager(Employee):
    """Manager with team and bonus."""
    
    def __init__(self, name, employee_id, salary, team_size=0):
        super().__init__(name, employee_id, salary)
        self.team_size = team_size
    
    def get_annual_salary(self):
        # Managers get bonus based on team size
        base = super().get_annual_salary()
        bonus = self.team_size * 1000  # $1000 per team member
        return base + bonus
    
    def __str__(self):
        return f"Manager {super().__str__()} - Team: {self.team_size}"
 
class Developer(Employee):
    """Developer with tech stack."""
    
    def __init__(self, name, employee_id, salary, languages):
        super().__init__(name, employee_id, salary)
        self.languages = languages
    
    def get_annual_salary(self):
        # Developers get extra for each language
        base = super().get_annual_salary()
        language_bonus = len(self.languages) * 500  # $500 per language
        return base + language_bonus
    
    def __str__(self):
        langs = ", ".join(self.languages)
        return f"Developer {super().__str__()} - Languages: {langs}"
 
# Create employees
manager = Manager("Alice", "M001", 8000, team_size=5)
dev1 = Developer("Bob", "D001", 6000, ["Python", "JavaScript", "SQL"])
dev2 = Developer("Charlie", "D002", 5500, ["Python"])
 
employees = [manager, dev1, dev2]
 
print("Employee Details:")
for emp in employees:
    print(f"  {emp}")
    print(f"    Annual Salary: ${emp.get_annual_salary():,}")