- What is inheritance — creating class hierarchies
- Parent and child classes — the relationship
- Method overriding — customizing behavior
- The super() function — calling parent methods
- Types of inheritance — single, multiple, multilevel
- Best practices — using inheritance effectively
What is Inheritance?
Inheritance is a fundamental concept in Object-Oriented Programming that allows a class to inherit attributes and methods from another class. It's like a family tree for your code — child classes inherit from parent classes, just like children inherit traits from their parents.
Think of inheritance like a recipe. You have a basic recipe for a cake. Then you have a special recipe for a chocolate cake that uses the same basic steps but adds chocolate. The chocolate cake inherits from the basic cake.
In Python, inheritance lets you create a hierarchy of classes. The class that provides the attributes and methods is called the parent class (or base class). The class that inherits is called the child class (or derived class).
💡 Key concept: Inheritance is a way to create a new class using an existing class as a starting point. It promotes code reuse and helps organize related classes in a hierarchy.
Parent and Child Classes
Creating a Class Hierarchy
To create inheritance, you define a child class that inherits from a parent class. The child class automatically gets all the methods and attributes of the parent class. You can then add new methods or override existing ones.
# Parent and Child Classes
# Parent class (Base class)
class Animal:
"""A class representing an animal"""
def __init__(self, name, species):
self.name = name
self.species = species
self.is_alive = True
def eat(self):
return f"{self.name} is eating"
def sleep(self):
return f"{self.name} is sleeping"
def get_info(self):
return f"{self.name} is a {self.species}"
# Child class (Derived class) that inherits from Animal
class Dog(Animal):
"""A class representing a dog, inherits from Animal"""
def __init__(self, name, breed):
# Call the parent class's __init__ method
super().__init__(name, "Dog")
self.breed = breed
def bark(self):
return f"{self.name} says Woof!"
def fetch(self):
return f"{self.name} is fetching the ball"
# Child class (Derived class) that inherits from Animal
class Cat(Animal):
"""A class representing a cat, inherits from Animal"""
def __init__(self, name, color):
super().__init__(name, "Cat")
self.color = color
def meow(self):
return f"{self.name} says Meow!"
def purr(self):
return f"{self.name} is purring"
# Creating objects from child classes
dog = Dog("Buddy", "Golden Retriever")
cat = Cat("Whiskers", "Orange")
# Using methods from the parent class
print(dog.get_info()) # Buddy is a Dog
print(cat.get_info()) # Whiskers is a Cat
print(dog.eat()) # Buddy is eating
print(cat.sleep()) # Whiskers is sleeping
# Using methods from the child classes
print(dog.bark()) # Buddy says Woof!
print(dog.fetch()) # Buddy is fetching the ball
print(cat.meow()) # Whiskers says Meow!
print(cat.purr()) # Whiskers is purring
Parent and child classes key points:
- Parent class — the class being inherited from
- Child class — the class that inherits
- Inheritance syntax — class Child(Parent):
- All parent methods — child classes get all parent methods
- Can add new methods — child classes can add new functionality
- Can override methods — child classes can change parent methods
Quick Check: What is the relationship between a parent and child class? (Answer: The child class inherits from the parent class)
Method Overriding
Customizing Behavior in Child Classes
Method overriding is when a child class provides its own implementation of a method that is already defined in the parent class. This allows child classes to have specialized behavior while still maintaining a common interface.
# Method Overriding
class Vehicle:
"""A class representing a vehicle"""
def __init__(self, make, model, year):
self.make = make
self.model = model
self.year = year
def start(self):
return "Starting the vehicle"
def stop(self):
return "Stopping the vehicle"
def get_info(self):
return f"{self.year} {self.make} {self.model}"
class Car(Vehicle):
"""A class representing a car, overriding some methods"""
def __init__(self, make, model, year, doors=4):
super().__init__(make, model, year)
self.doors = doors
# Override the start method
def start(self):
return "Starting the car with a key"
# Override the get_info method
def get_info(self):
return f"{self.year} {self.make} {self.model} with {self.doors} doors"
class Motorcycle(Vehicle):
"""A class representing a motorcycle, overriding some methods"""
def __init__(self, make, model, year, has_sidecar=False):
super().__init__(make, model, year)
self.has_sidecar = has_sidecar
# Override the start method
def start(self):
return "Starting the motorcycle with a kick-start"
# Override the get_info method
def get_info(self):
sidecar = "with sidecar" if self.has_sidecar else "without sidecar"
return f"{self.year} {self.make} {self.model} {sidecar}"
# Creating objects
car = Car("Toyota", "Camry", 2022)
bike = Motorcycle("Harley", "Sportster", 2021)
# Using overridden methods
print(car.start()) # Starting the car with a key
print(bike.start()) # Starting the motorcycle with a kick-start
# Using overridden get_info
print(car.get_info()) # 2022 Toyota Camry with 4 doors
print(bike.get_info()) # 2021 Harley Sportster without sidecar
# Parent methods can still be called using super()
class ElectricCar(Car):
def __init__(self, make, model, year, doors=4, battery_range=300):
super().__init__(make, model, year, doors)
self.battery_range = battery_range
def start(self):
# Call parent method and add more
parent_start = super().start()
return f"{parent_start} silently"
def get_info(self):
return f"{super().get_info()} with {self.battery_range} mile range"
tesla = ElectricCar("Tesla", "Model 3", 2023, 4, 350)
print(tesla.start()) # Starting the car with a key silently
print(tesla.get_info()) # 2023 Tesla Model 3 with 4 doors with 350 mile range
Method overriding key points:
- Same name — overriding method has the same name as the parent
- Different implementation — child class provides its own version
- Parent method still exists — can be called using super()
- Signature should match — parameters and return type
- Used for specialization — customizing behavior for specific types
Quick Check: What is method overriding? (Answer: When a child class provides its own implementation of a parent method)
The super() Function
Calling Methods from the Parent Class
The super() function is used to call methods from the parent class. It's especially useful when you want to extend or customize the parent's behavior while still using its functionality.
# Using super() to call parent methods
class Employee:
"""A class representing an employee"""
def __init__(self, name, employee_id):
self.name = name
self.employee_id = employee_id
self.position = "Employee"
def work(self):
return f"{self.name} is working"
def get_info(self):
return f"{self.name} (ID: {self.employee_id}) - {self.position}"
class Manager(Employee):
"""A class representing a manager"""
def __init__(self, name, employee_id, team_size):
# Call parent __init__ using super()
super().__init__(name, employee_id)
self.position = "Manager"
self.team_size = team_size
def work(self):
# Call parent work method and add more
parent_work = super().work()
return f"{parent_work} and managing {self.team_size} people"
def get_info(self):
# Use parent get_info and add more
return f"{super().get_info()} with team of {self.team_size}"
class Developer(Employee):
"""A class representing a developer"""
def __init__(self, name, employee_id, programming_language):
super().__init__(name, employee_id)
self.position = "Developer"
self.programming_language = programming_language
def work(self):
parent_work = super().work()
return f"{parent_work} in {self.programming_language}"
def code(self):
return f"{self.name} is writing {self.programming_language} code"
# Using the classes
manager = Manager("Alice", "M001", 5)
developer = Developer("Bob", "D001", "Python")
print(manager.work()) # Alice is working and managing 5 people
print(developer.work()) # Bob is working in Python
print(manager.get_info()) # Alice (ID: M001) - Manager with team of 5
print(developer.get_info()) # Bob (ID: D001) - Developer
print(developer.code()) # Bob is writing Python code
# super() in multiple inheritance
class LoggingMixin:
def log(self, message):
print(f"LOG: {message}")
class Database:
def save(self):
print("Saving to database")
class User(LoggingMixin, Database):
def save(self):
super().log("Saving user...")
super().save()
print("User saved successfully")
user = User()
user.save()
# LOG: Saving user...
# Saving to database
# User saved successfully
super() key points:
- Calls parent methods — allows you to use parent class functionality
- Used in __init__ — to initialize parent attributes
- Used in method overriding — to extend parent methods
- Works in multiple inheritance — follows MRO (Method Resolution Order)
- Makes code maintainable — changes to parent class propagate automatically
Quick Check: What does super() do? (Answer: It calls methods from the parent class)
Types of Inheritance
Different Inheritance Patterns
Python supports several types of inheritance patterns. Each pattern serves a different purpose and is useful in different situations. Let's look at the main types.
# Types of Inheritance
# 1. Single Inheritance
# A child class inherits from one parent class
class Animal:
def __init__(self, name):
self.name = name
def eat(self):
return f"{self.name} is eating"
class Dog(Animal):
def bark(self):
return f"{self.name} says Woof!"
# 2. Multiple Inheritance
# A child class inherits from multiple parent classes
class Flyable:
def fly(self):
return "Flying"
class Swimmable:
def swim(self):
return "Swimming"
class Duck(Flyable, Swimmable):
def __init__(self, name):
self.name = name
def quack(self):
return f"{self.name} says Quack!"
# 3. Multilevel Inheritance
# A child class inherits from another child class
class Animal:
def __init__(self, name):
self.name = name
class Mammal(Animal):
def feed_milk(self):
return f"{self.name} is feeding milk"
class Dog(Mammal):
def bark(self):
return f"{self.name} says Woof!"
# 4. Hierarchical Inheritance
# Multiple child classes inherit from a single parent class
class Shape:
def area(self):
pass
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14159 * self.radius ** 2
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
# 5. Hybrid Inheritance
# A combination of multiple and multilevel inheritance
class A:
pass
class B(A):
pass
class C(A):
pass
class D(B, C):
pass
# Demonstration
print("=== Single Inheritance ===")
dog = Dog("Buddy")
print(dog.eat()) # Buddy is eating
print(dog.bark()) # Buddy says Woof!
print("\n=== Multiple Inheritance ===")
duck = Duck("Donald")
print(duck.fly()) # Flying
print(duck.swim()) # Swimming
print(duck.quack()) # Donald says Quack!
print("\n=== Multilevel Inheritance ===")
dog2 = Dog("Max")
print(dog2.feed_milk()) # Max is feeding milk
print(dog2.bark()) # Max says Woof!
print("\n=== Hierarchical Inheritance ===")
circle = Circle(5)
rect = Rectangle(4, 6)
print(f"Circle area: {circle.area():.2f}") # 78.54
print(f"Rectangle area: {rect.area()}") # 24
Types of inheritance:
- Single — one parent, one child
- Multiple — child inherits from multiple parents
- Multilevel — chain of inheritance (grandparent → parent → child)
- Hierarchical — multiple children from one parent
- Hybrid — combination of multiple and multilevel
Quick Check: What is the difference between multiple and multilevel inheritance? (Answer: Multiple inheritance has one child with multiple parents; multilevel inheritance has a chain of parent-child relationships)
Multiple Inheritance
Inheriting from Multiple Parents
Multiple inheritance is a feature where a class can inherit from more than one parent class. This allows a class to combine the functionality of multiple classes. However, it also introduces complexity, especially when parent classes have methods with the same name.
# Multiple Inheritance in detail
# Example: A multimedia player
class AudioPlayer:
def __init__(self):
self.volume = 50
def play_audio(self, file):
return f"Playing audio: {file}"
def adjust_volume(self, level):
self.volume = level
return f"Volume set to {level}"
class VideoPlayer:
def __init__(self):
self.brightness = 70
def play_video(self, file):
return f"Playing video: {file}"
def adjust_brightness(self, level):
self.brightness = level
return f"Brightness set to {level}"
class MediaPlayer(AudioPlayer, VideoPlayer):
def __init__(self):
# Call parent constructors
AudioPlayer.__init__(self)
VideoPlayer.__init__(self)
self.current_file = None
def play(self, file):
self.current_file = file
if file.endswith(('.mp3', '.wav')):
return self.play_audio(file)
elif file.endswith(('.mp4', '.avi')):
return self.play_video(file)
else:
return "Unsupported file format"
# Using the MediaPlayer
player = MediaPlayer()
print(player.play("song.mp3")) # Playing audio: song.mp3
print(player.play("movie.mp4")) # Playing video: movie.mp4
print(player.adjust_volume(75)) # Volume set to 75
print(player.adjust_brightness(80)) # Brightness set to 80
# Example: A smart device
class SmartDevice:
def __init__(self):
self.is_on = False
def turn_on(self):
self.is_on = True
return "Device turned on"
def turn_off(self):
self.is_on = False
return "Device turned off"
class Camera:
def take_photo(self):
return "Photo taken"
def record_video(self):
return "Video recording"
class Phone(SmartDevice, Camera):
def __init__(self):
SmartDevice.__init__(self)
self.battery = 100
def make_call(self, number):
return f"Calling {number}"
def charge(self):
self.battery = 100
return "Phone charged"
phone = Phone()
print(phone.turn_on()) # Device turned on
print(phone.take_photo()) # Photo taken
print(phone.make_call("123-456-7890")) # Calling 123-456-7890
# Method Resolution Order (MRO)
print("\nMRO for MediaPlayer:")
for cls in MediaPlayer.__mro__:
print(f" {cls.__name__}")
# MediaPlayer → AudioPlayer → VideoPlayer → object
Multiple inheritance key points:
- Multiple parents — child inherits from two or more classes
- Combines functionality — gets methods from all parents
- Method Resolution Order (MRO) — determines which parent method is called first
- Constructor calls — need to call each parent's __init__
- Use with caution — can lead to complexity and confusion
Quick Check: What is MRO in multiple inheritance? (Answer: Method Resolution Order — the order in which parent classes are searched for methods)
Multilevel Inheritance
Chain of Inheritance
Multilevel inheritance is when a class inherits from another class, which in turn inherits from another class. This creates a chain of inheritance, where each level adds more specific functionality.
# Multilevel Inheritance
class Animal:
"""Base class"""
def __init__(self, name):
self.name = name
def eat(self):
return f"{self.name} is eating"
def sleep(self):
return f"{self.name} is sleeping"
class Mammal(Animal):
"""Inherits from Animal"""
def __init__(self, name, fur_color):
super().__init__(name)
self.fur_color = fur_color
def feed_milk(self):
return f"{self.name} is feeding milk"
def warm_blooded(self):
return True
class Dog(Mammal):
"""Inherits from Mammal"""
def __init__(self, name, fur_color, breed):
super().__init__(name, fur_color)
self.breed = breed
def bark(self):
return f"{self.name} says Woof!"
def fetch(self):
return f"{self.name} is fetching"
class Puppy(Dog):
"""Inherits from Dog"""
def __init__(self, name, fur_color, breed, age):
super().__init__(name, fur_color, breed)
self.age = age
def play(self):
return f"{self.name} is playing"
def bark(self):
# Override bark method
return f"{self.name} says Yip Yip!"
# Creating a Puppy object
puppy = Puppy("Max", "Golden", "Golden Retriever", 3)
# Methods from Animal
print(puppy.eat()) # Max is eating
print(puppy.sleep()) # Max is sleeping
# Methods from Mammal
print(puppy.feed_milk()) # Max is feeding milk
print(f"Warm blooded: {puppy.warm_blooded()}") # True
# Methods from Dog
print(puppy.bark()) # Max says Yip Yip! (overridden)
print(puppy.fetch()) # Max is fetching
# Methods from Puppy
print(puppy.play()) # Max is playing
# Inheritance chain:
# Puppy → Dog → Mammal → Animal → object
# Checking the inheritance chain
print(f"Is Puppy a Dog? {isinstance(puppy, Dog)}") # True
print(f"Is Puppy a Mammal? {isinstance(puppy, Mammal)}") # True
print(f"Is Puppy an Animal? {isinstance(puppy, Animal)}") # True
print(f"Is Dog a Mammal? {issubclass(Dog, Mammal)}") # True
Multilevel inheritance key points:
- Chain of inheritance — classes form a hierarchy
- Each level adds — more specific functionality
- Inherits from ancestors — gets methods from all parent classes
- Method overriding — each level can override methods
- Isinstance and issubclass — check inheritance relationships
Quick Check: What is multilevel inheritance? (Answer: A chain of inheritance where a class inherits from another class that inherits from another class)
Best Practices for Inheritance
Using Inheritance Effectively
# Best practices for inheritance
# 1. Use inheritance for "is-a" relationships
# Good: Dog IS-A Animal
class Animal: pass
class Dog(Animal): pass
# Bad: Car IS-A Engine (should be composition)
# class Car(Engine): pass # Wrong relationship
# 2. Keep the hierarchy shallow
# Good: 2-3 levels deep
class Animal: pass
class Mammal(Animal): pass
class Dog(Mammal): pass
# Bad: Very deep hierarchy (5+ levels)
# class A: pass
# class B(A): pass
# class C(B): pass
# class D(C): pass
# class E(D): pass
# 3. Use super() to call parent methods
class Parent:
def __init__(self, name):
self.name = name
class Child(Parent):
def __init__(self, name, age):
super().__init__(name) # Good
self.age = age
# Bad: Hardcoding parent name
# class Child(Parent):
# def __init__(self, name, age):
# Parent.__init__(self, name) # Hardcoded
# 4. Follow the Liskov Substitution Principle
# Subclasses should be substitutable for their parent classes
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
class Square(Rectangle):
def __init__(self, side):
super().__init__(side, side)
# Good: Square IS-A Rectangle
# Square can be used wherever Rectangle is expected
# 5. Use composition when inheritance doesn't fit
# Composition: A class uses another class
class Engine:
def start(self):
return "Engine started"
class Car:
def __init__(self):
self.engine = Engine() # Composition
def start(self):
return self.engine.start()
# 6. Document inheritance relationships
class Employee:
"""
Base class for all employees.
Subclasses:
Manager: Manages teams
Developer: Writes code
Designer: Creates designs
"""
pass
# 7. Keep classes focused
# Each class should have a single responsibility
class DataProcessor:
"""Only processes data"""
pass
class DataValidator:
"""Only validates data"""
pass
# Bad: One class does everything
class DataManager:
"""Processes, validates, and stores data"""
pass
Best practices summary:
- Is-a relationships — inheritance for "is-a" relationships
- Keep hierarchies shallow — avoid deep inheritance chains
- Use super() — for calling parent methods
- Liskov Substitution — subclasses should be substitutable
- Prefer composition — when inheritance doesn't fit
- Document relationships — explain inheritance hierarchies
Quick Check: What is the Liskov Substitution Principle? (Answer: Subclasses should be substitutable for their parent classes)
Try It Yourself
Experiment with inheritance in the editor below.
INHERITANCE PRACTICE
========================================
1. BASIC INHERITANCE
Hi, I'm Alice, 20 years old
Alice is studying
2. METHOD OVERRIDING
Dog: Woof!
Cat: Meow!
3. USING SUPER()
Vehicle starting car
Inheritance practice complete!
You've Got It!
You now understand inheritance in Python. You know how to create parent and child classes, override methods, use super(), and work with different inheritance patterns.
Quick Quiz
Test what you've learned:
Frequently Asked Questions
What is the difference between inheritance and composition?
What is the Method Resolution Order (MRO)?
__mro__ attribute or the mro() method.
What is the Liskov Substitution Principle?
What's a common interview question about inheritance?
Can a child class have multiple parents?
What happens if a child class doesn't override a method?
Where to Go From Here
Now that you understand inheritance, check out these related topics:
Single Inheritance
Learn more about single inheritance in detail.
Learn More →Multiple Inheritance
Learn about multiple inheritance and the diamond problem.
Learn More →Method Overriding
Learn more about overriding methods in subclasses.
Learn More →