Inheritance
Inheritance is a fundamental OOP concept that allows a class (child/derived class) to inherit attributes and methods from another class (parent/base class). This promotes code reusability and establishes a relationship between classes.
Basic Inheritance
In Python, inheritance is achieved by passing the parent class as an argument to the child class definition:
class Animal: def __init__(self, name): self.name = name
def speak(self): return f"{self.name} makes a sound"
class Dog(Animal): def speak(self): return f"{self.name} barks"
class Cat(Animal): def speak(self): return f"{self.name} meows"
dog = Dog("Buddy")cat = Cat("Whiskers")
print(dog.speak()) # Buddy barksprint(cat.speak()) # Whiskers meowsTypes of Inheritance
Single Inheritance
A child class inherits from a single parent class:
class Vehicle: def __init__(self, brand, model): self.brand = brand self.model = model
def start(self): return f"{self.brand} {self.model} started"
class Car(Vehicle): def __init__(self, brand, model, doors): super().__init__(brand, model) self.doors = doors
def honk(self): return "Beep beep!"
car = Car("Toyota", "Camry", 4)print(car.start()) # Toyota Camry startedprint(car.honk()) # Beep beep!Multiple Inheritance
A child class inherits from multiple parent classes:
class Flyable: def fly(self): return "Flying high!"
class Swimmable: def swim(self): return "Swimming deep!"
class Duck(Flyable, Swimmable): def __init__(self, name): self.name = name
def quack(self): return f"{self.name} says quack!"
duck = Duck("Donald")print(duck.fly()) # Flying high!print(duck.swim()) # Swimming deep!print(duck.quack()) # Donald says quack!Multilevel Inheritance
A class inherits from a child class, creating a chain:
class Animal: def __init__(self, name): self.name = name
class Mammal(Animal): def give_birth(self): return f"{self.name} gives birth to live young"
class Dog(Mammal): def bark(self): return f"{self.name} barks"
dog = Dog("Buddy")print(dog.name) # Buddy (from Animal)print(dog.give_birth()) # Buddy gives birth to live young (from Mammal)print(dog.bark()) # Buddy barks (from Dog)Hierarchical Inheritance
Multiple child classes inherit from a single parent class:
class Shape: def __init__(self, name): self.name = name
def area(self): return 0
class Rectangle(Shape): def __init__(self, name, length, width): super().__init__(name) self.length = length self.width = width
def area(self): return self.length * self.width
class Circle(Shape): def __init__(self, name, radius): super().__init__(name) self.radius = radius
def area(self): return 3.14159 * self.radius ** 2
rect = Rectangle("Rectangle", 5, 3)circle = Circle("Circle", 4)
print(f"{rect.name} area: {rect.area()}") # Rectangle area: 15print(f"{circle.name} area: {circle.area()}") # Circle area: 50.26544Method Overriding
Method overriding occurs when a child class provides its own implementation of a method that exists in the parent class:
class Animal: def make_sound(self): return "Some generic sound"
class Dog(Animal): def make_sound(self): return "Woof!"
class Cat(Animal): def make_sound(self): return "Meow!"
animal = Animal()dog = Dog()cat = Cat()
print(animal.make_sound()) # Some generic soundprint(dog.make_sound()) # Woof!print(cat.make_sound()) # Meow!The super() Function
The super() function allows you to call methods from the parent class. It’s particularly useful in constructors:
class Person: def __init__(self, name, age): self.name = name self.age = age
def display(self): return f"Name: {self.name}, Age: {self.age}"
class Student(Person): def __init__(self, name, age, student_id): super().__init__(name, age) # Call parent constructor self.student_id = student_id
def display(self): parent_info = super().display() # Call parent method return f"{parent_info}, Student ID: {self.student_id}"
student = Student("Alice", 20, "S12345")print(student.display()) # Name: Alice, Age: 20, Student ID: S12345Using super() with Multiple Inheritance
With multiple inheritance, super() follows the Method Resolution Order (MRO):
class A: def method(self): return "A"
class B(A): def method(self): return f"B -> {super().method()}"
class C(A): def method(self): return f"C -> {super().method()}"
class D(B, C): def method(self): return f"D -> {super().method()}"
obj = D()print(obj.method()) # D -> B -> C -> AMethod Resolution Order (MRO)
Method Resolution Order (MRO) determines the order in which Python searches for methods in inheritance hierarchies. Python uses the C3 linearization algorithm.
You can view the MRO using the __mro__ attribute or mro() method:
class A: pass
class B(A): pass
class C(A): pass
class D(B, C): pass
print(D.__mro__)# (<class '__main__.D'>, <class '__main__.B'>,# <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
print(D.mro())# Same output as aboveUnderstanding MRO in Multiple Inheritance
class First: def method(self): return "First"
class Second: def method(self): return "Second"
class Third(First, Second): pass
class Fourth(Second, First): pass
obj1 = Third()obj2 = Fourth()
print(obj1.method()) # First (First comes before Second in MRO)print(obj2.method()) # Second (Second comes before First in MRO)
print(Third.__mro__)# (<class '__main__.Third'>, <class '__main__.First'>,# <class '__main__.Second'>, <class 'object'>)The isinstance() and issubclass() Functions
isinstance(obj, class): Checks if an object is an instance of a class or its subclassesissubclass(class1, class2): Checks if a class is a subclass of another class
class Animal: pass
class Dog(Animal): pass
class Cat(Animal): pass
dog = Dog()cat = Cat()
print(isinstance(dog, Dog)) # Trueprint(isinstance(dog, Animal)) # True (Dog inherits from Animal)print(isinstance(dog, Cat)) # False
print(issubclass(Dog, Animal)) # Trueprint(issubclass(Cat, Animal)) # Trueprint(issubclass(Dog, Cat)) # FalseAccessing Parent Class Attributes
You can access parent class attributes directly or through super():
class Parent: class_var = "Parent class variable"
def __init__(self): self.instance_var = "Parent instance variable"
class Child(Parent): class_var = "Child class variable"
def __init__(self): super().__init__() self.instance_var = "Child instance variable"
def show_parent_class_var(self): return Parent.class_var # Access parent class variable directly
def show_parent_instance_var(self): return super().instance_var # Access parent instance variable
child = Child()print(child.instance_var) # Child instance variableprint(child.show_parent_instance_var()) # Parent instance variableprint(child.class_var) # Child class variableprint(child.show_parent_class_var()) # Parent class variableOverriding Special Methods
You can override special methods (magic methods) in child classes:
class Animal: def __init__(self, name): self.name = name
def __str__(self): return f"Animal: {self.name}"
class Dog(Animal): def __init__(self, name, breed): super().__init__(name) self.breed = breed
def __str__(self): return f"Dog: {self.name} ({self.breed})"
dog = Dog("Buddy", "Golden Retriever")print(dog) # Dog: Buddy (Golden Retriever)Complete Example: Employee Hierarchy
class Employee: company_name = "Tech Corp" employee_count = 0
def __init__(self, name, employee_id): self.name = name self.employee_id = employee_id Employee.employee_count += 1
def get_info(self): return f"ID: {self.employee_id}, Name: {self.name}"
@classmethod def get_employee_count(cls): return cls.employee_count
class Manager(Employee): def __init__(self, name, employee_id, department): super().__init__(name, employee_id) self.department = department
def get_info(self): base_info = super().get_info() return f"{base_info}, Department: {self.department}, Role: Manager"
class Developer(Employee): def __init__(self, name, employee_id, programming_language): super().__init__(name, employee_id) self.programming_language = programming_language
def get_info(self): base_info = super().get_info() return f"{base_info}, Language: {self.programming_language}, Role: Developer"
class SeniorDeveloper(Developer): def __init__(self, name, employee_id, programming_language, years_experience): super().__init__(name, employee_id, programming_language) self.years_experience = years_experience
def get_info(self): base_info = super().get_info() return f"{base_info}, Experience: {self.years_experience} years, Level: Senior"
manager = Manager("Alice", "M001", "Engineering")dev1 = Developer("Bob", "D001", "Python")dev2 = SeniorDeveloper("Charlie", "D002", "Python", 5)
print(manager.get_info())# ID: M001, Name: Alice, Department: Engineering, Role: Manager
print(dev1.get_info())# ID: D001, Name: Bob, Language: Python, Role: Developer
print(dev2.get_info())# ID: D002, Name: Charlie, Language: Python, Role: Developer, Experience: 5 years, Level: Senior
print(f"Total employees: {Employee.get_employee_count()}") # 3Diamond Problem in Multiple Inheritance
Python handles the diamond problem (when a class inherits from two classes that both inherit from the same base class) using MRO:
class A: def method(self): return "A"
class B(A): def method(self): return f"B -> {super().method()}"
class C(A): def method(self): return f"C -> {super().method()}"
class D(B, C): def method(self): return f"D -> {super().method()}"
obj = D()print(obj.method()) # D -> B -> C -> A
print(D.__mro__)# (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>,# <class '__main__.A'>, <class 'object'>)Notice that A appears only once in the MRO, preventing the diamond problem.
Exercises
Exercise 1: Basic Inheritance
Create a parent class Vehicle with brand and model attributes, and a start() method that returns “Vehicle started”. Create a child class Car that inherits from Vehicle and adds a honk() method. Read brand and model, create a Car object, and call both methods.
Basic Inheritance
class Vehicle: def __init__(self, brand, model): self.brand = brand self.model = model
def start(self): return "Vehicle started"
class Car(Vehicle): def honk(self): return "Beep beep!"
brand = input()model = input()car = Car(brand, model)print(car.start())print(car.honk())Exercise 2: Method Overriding
Create a parent class Shape with an area() method that returns 0. Create child classes Rectangle and Circle that override area() to calculate their respective areas. Read values and create objects, then print their areas.
Method Overriding
import math
class Shape: def area(self): return 0
class Rectangle(Shape): def __init__(self, length, width): self.length = length self.width = width
def area(self): return self.length * self.width
class Circle(Shape): def __init__(self, radius): self.radius = radius
def area(self): return math.pi * self.radius ** 2
length = float(input())width = float(input())radius = float(input())
rect = Rectangle(length, width)circle = Circle(radius)
print(f"Rectangle area: {rect.area()}")print(f"Circle area: {circle.area():.2f}")Exercise 3: Using super()
Create a parent class Person with name and age, and a display() method. Create a child class Student that adds student_id and overrides display() to include the student ID using super().
Using super()
class Person: def __init__(self, name, age): self.name = name self.age = age
def display(self): return f"Name: {self.name}, Age: {self.age}"
class Student(Person): def __init__(self, name, age, student_id): super().__init__(name, age) self.student_id = student_id
def display(self): return f"{super().display()}, Student ID: {self.student_id}"
name = input()age = int(input())student_id = input()
student = Student(name, age, student_id)print(student.display())Exercise 4: Multiple Inheritance
Create two parent classes: Flyable with a fly() method and Swimmable with a swim() method. Create a child class Duck that inherits from both and has a name attribute. Read the name and call all three methods.
Multiple Inheritance
class Flyable: def fly(self): return "Flying high!"
class Swimmable: def swim(self): return "Swimming deep!"
class Duck(Flyable, Swimmable): def __init__(self, name): self.name = name
def quack(self): return f"{self.name} says quack!"
name = input()duck = Duck(name)print(duck.fly())print(duck.swim())print(duck.quack())Exercise 5: isinstance and issubclass
Create a parent class Animal and child classes Dog and Cat. Create a Dog object and use isinstance() to check if it’s an instance of Dog, Animal, and Cat. Use issubclass() to check if Dog and Cat are subclasses of Animal.
isinstance and issubclass
class Animal: pass
class Dog(Animal): pass
class Cat(Animal): pass
dog = Dog()
print(isinstance(dog, Dog))print(isinstance(dog, Animal))print(isinstance(dog, Cat))print(issubclass(Dog, Animal))print(issubclass(Cat, Animal))