🧬 Class Inheritance
Class inheritance allows you to create new classes that build upon existing ones, inheriting their attributes and methods while adding specialized functionality. Instead of writing everything from scratch, inheritance lets you extend and customize existing code, creating hierarchies of related classes that share common features.
Think of inheritance like family traits - children inherit characteristics from their parents but also develop their own unique features. In programming, a child class inherits from a parent class but can add new methods or modify existing ones.
# Basic inheritance example
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return f"{self.name} makes a sound"
def sleep(self):
return f"{self.name} is sleeping"
class Dog(Animal): # Dog inherits from Animal
def speak(self): # Override parent method
return f"{self.name} barks!"
def fetch(self): # Add new method
return f"{self.name} fetches the ball"
# Using inheritance
my_dog = Dog("Buddy")
print(my_dog.speak()) # Uses overridden method
print(my_dog.sleep()) # Uses inherited method
print(my_dog.fetch()) # Uses new method
🎯 Understanding Inheritance
Inheritance creates an "is-a" relationship between classes, where a child class is a specialized version of its parent class. The child class automatically gets all the functionality of the parent class and can extend or modify it as needed.
Creating Child Classes
To create a child class, specify the parent class in parentheses after the child class name. The child automatically inherits all parent attributes and methods.
class Vehicle:
def __init__(self, brand, model):
self.brand = brand
self.model = model
self.fuel = 100
def start(self):
return f"{self.brand} {self.model} is starting"
def stop(self):
return f"{self.brand} {self.model} has stopped"
def get_info(self):
return f"{self.brand} {self.model} (Fuel: {self.fuel}%)"
class Car(Vehicle): # Car inherits from Vehicle
def __init__(self, brand, model, doors):
super().__init__(brand, model) # Call parent constructor
self.doors = doors # Add car-specific attribute
def honk(self): # Add car-specific method
return f"{self.brand} {self.model} honks the horn!"
# Using the child class
my_car = Car("Toyota", "Camry", 4)
print(my_car.start()) # Inherited method
print(my_car.honk()) # New method
print(my_car.get_info()) # Inherited method
print(f"Doors: {my_car.doors}") # New attribute
The super() Function
The super()
function provides access to the parent class, allowing you to call parent methods from child classes. This is especially useful in constructors and when extending parent functionality.
class Employee:
def __init__(self, name, employee_id):
self.name = name
self.employee_id = employee_id
self.salary = 0
def work(self):
return f"{self.name} is working"
def get_details(self):
return f"Employee {self.employee_id}: {self.name} (${self.salary})"
class Manager(Employee):
def __init__(self, name, employee_id, department):
super().__init__(name, employee_id) # Initialize parent
self.department = department
self.team_size = 0
def work(self):
parent_work = super().work() # Get parent behavior
return f"{parent_work} and managing {self.department}"
def hire(self, employee):
self.team_size += 1
return f"{self.name} hired {employee} to {self.department}"
# Using super() functionality
manager = Manager("Alice", "M001", "Engineering")
manager.salary = 80000
print(manager.work()) # Extended behavior
print(manager.get_details()) # Inherited method
print(manager.hire("Bob")) # New method
⚡ Method Overriding
Method overriding allows child classes to provide specialized implementations of parent methods. The child class method replaces the parent version, enabling customized behavior while maintaining the same interface.
Overriding Parent Methods
When a child class defines a method with the same name as a parent method, the child version takes precedence. This allows specialized behavior while keeping the same method names and calling patterns.
class Shape:
def __init__(self, color):
self.color = color
def area(self):
return 0 # Default implementation
def describe(self):
return f"A {self.color} shape with area {self.area()}"
class Rectangle(Shape):
def __init__(self, color, width, height):
super().__init__(color)
self.width = width
self.height = height
def area(self): # Override parent method
return self.width * self.height
class Circle(Shape):
def __init__(self, color, radius):
super().__init__(color)
self.radius = radius
def area(self): # Override parent method
return 3.14 * self.radius * self.radius
# Each shape calculates area differently
rect = Rectangle("blue", 5, 3)
circle = Circle("red", 4)
print(rect.describe()) # Uses Rectangle's area method
print(circle.describe()) # Uses Circle's area method
Extending Parent Methods
Sometimes you want to add to parent functionality rather than replace it completely. Using super()
allows you to call the parent method and then add additional behavior.
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner
self.balance = balance
self.transactions = []
def deposit(self, amount):
self.balance += amount
self.transactions.append(f"Deposited ${amount}")
return f"Deposited ${amount}. Balance: ${self.balance}"
class SavingsAccount(BankAccount):
def __init__(self, owner, balance=0, interest_rate=0.02):
super().__init__(owner, balance)
self.interest_rate = interest_rate
def deposit(self, amount):
# Extend parent functionality
result = super().deposit(amount) # Call parent method
if amount >= 1000: # Add bonus for large deposits
bonus = amount * 0.01
self.balance += bonus
self.transactions.append(f"Bonus: ${bonus}")
result += f" + ${bonus} bonus!"
return result
def add_interest(self):
interest = self.balance * self.interest_rate
self.balance += interest
self.transactions.append(f"Interest: ${interest}")
return f"Added ${interest} interest"
# Extended functionality in action
savings = SavingsAccount("Alice", 500, 0.03)
print(savings.deposit(1500)) # Gets bonus
print(savings.add_interest()) # New method
🚀 Multiple Inheritance
Python supports multiple inheritance, where a class can inherit from multiple parent classes. This powerful feature allows combining functionality from different sources but requires careful design to avoid conflicts.
Combining Multiple Parents
Multiple inheritance allows a class to inherit from several parent classes, gaining functionality from all of them.
class Flyable:
def fly(self):
return f"{self.name} is flying"
def land(self):
return f"{self.name} has landed"
class Swimmable:
def swim(self):
return f"{self.name} is swimming"
def dive(self):
return f"{self.name} is diving"
class Duck(Flyable, Swimmable): # Multiple inheritance
def __init__(self, name):
self.name = name
def quack(self):
return f"{self.name} says quack!"
# Duck can fly and swim
donald = Duck("Donald")
print(donald.quack()) # Own method
print(donald.fly()) # From Flyable
print(donald.swim()) # From Swimmable
print(donald.land()) # From Flyable
print(donald.dive()) # From Swimmable
Method Resolution Order (MRO)
When multiple parent classes have methods with the same name, Python uses Method Resolution Order (MRO) to determine which method to call.
class A:
def greet(self):
return "Hello from A"
class B:
def greet(self):
return "Hello from B"
class C(A, B): # Inherits from both A and B
pass
class D(B, A): # Different order
pass
# MRO determines which method is called
c = C()
d = D()
print(c.greet()) # Uses A's method (A comes first)
print(d.greet()) # Uses B's method (B comes first)
# Check the method resolution order
print(f"C's MRO: {[cls.__name__ for cls in C.__mro__]}")
print(f"D's MRO: {[cls.__name__ for cls in D.__mro__]}")
🌟 Practical Applications
Building Class Hierarchies
Inheritance enables creating logical hierarchies that model real-world relationships.
class User:
def __init__(self, username, email):
self.username = username
self.email = email
self.active = True
def login(self):
return f"{self.username} logged in"
def logout(self):
return f"{self.username} logged out"
class Customer(User):
def __init__(self, username, email):
super().__init__(username, email)
self.orders = []
self.loyalty_points = 0
def place_order(self, order):
self.orders.append(order)
self.loyalty_points += 10
return f"Order placed by {self.username}"
class Admin(User):
def __init__(self, username, email, permissions):
super().__init__(username, email)
self.permissions = permissions
def manage_users(self, action, user):
if "user_management" in self.permissions:
return f"{self.username} performed {action} on {user}"
return "Permission denied"
# Different user types with specialized behavior
customer = Customer("alice", "alice@email.com")
admin = Admin("admin", "admin@email.com", ["user_management", "system_admin"])
print(customer.login())
print(customer.place_order("laptop"))
print(admin.manage_users("activate", "bob"))
💡 Design Patterns with Inheritance
Template Method Pattern
Use inheritance to define a skeleton algorithm while allowing subclasses to override specific steps.
class DataProcessor:
def process(self, data):
# Template method defining the algorithm
cleaned = self.clean_data(data)
processed = self.transform_data(cleaned)
result = self.save_data(processed)
return result
def clean_data(self, data):
# Default implementation
return [item.strip() for item in data if item.strip()]
def transform_data(self, data):
# Must be implemented by subclasses
raise NotImplementedError("Subclasses must implement transform_data")
def save_data(self, data):
# Default implementation
return f"Saved {len(data)} items"
class NumberProcessor(DataProcessor):
def transform_data(self, data):
return [float(item) for item in data if item.replace('.', '').isdigit()]
class TextProcessor(DataProcessor):
def transform_data(self, data):
return [item.upper() for item in data]
# Template pattern in action
number_processor = NumberProcessor()
text_processor = TextProcessor()
numbers = ["1.5", "2.0", "invalid", "3.7"]
texts = ["hello", "world", "python"]
print(number_processor.process(numbers))
print(text_processor.process(texts))
Hands-on Exercise
Create a simple Animal inheritance system. Make a base Animal class with name and age attributes, and a speak() method. Then create Dog and Cat classes that inherit from Animal and override the speak() method with their specific sounds.
class Animal:
def __init__(self, name, age):
# TODO: Set name and age attributes
pass
def speak(self):
# TODO: Generic animal sound
pass
def get_info(self):
# TODO: Return animal information
pass
class Dog(Animal):
def __init__(self, name, age):
# TODO: Call parent constructor
pass
def speak(self):
# TODO: Override with dog sound
pass
class Cat(Animal):
def __init__(self, name, age):
# TODO: Call parent constructor
pass
def speak(self):
# TODO: Override with cat sound
pass
# TODO: Test your classes
dog = Dog("Buddy", 5)
cat = Cat("Whiskers", 3)
print(dog.get_info())
print(dog.speak())
print(cat.get_info())
print(cat.speak())
Solution and Explanation 💡
Click to see the complete solution
class Animal:
def __init__(self, name, age):
# Set name and age attributes
self.name = name
self.age = age
def speak(self):
# Generic animal sound
return f"{self.name} makes a sound"
def get_info(self):
# Return animal information
return f"{self.name} is {self.age} years old"
class Dog(Animal):
def __init__(self, name, age):
# Call parent constructor
super().__init__(name, age)
def speak(self):
# Override with dog sound
return f"{self.name} says Woof!"
class Cat(Animal):
def __init__(self, name, age):
# Call parent constructor
super().__init__(name, age)
def speak(self):
# Override with cat sound
return f"{self.name} says Meow!"
# Test your classes
dog = Dog("Buddy", 5)
cat = Cat("Whiskers", 3)
print(dog.get_info())
print(dog.speak())
print(cat.get_info())
print(cat.speak())
Key Learning Points:
- 📌 Inheritance: Child classes inherit attributes and methods from parent class
- 📌 super(): Use
super().__init__()
to call parent constructor - 📌 Method overriding: Child classes can provide their own version of parent methods
- 📌 Code reuse: Common functionality stays in parent class, specific behavior in child classes
- 📌 Polymorphism: Different objects can respond to same method call differently
Test Your Knowledge
Test what you've learned about class inheritance:
What's Next?
Now that you understand inheritance basics, you're ready to dive deeper into method overriding. This technique allows you to customize inherited methods to create specialized behaviors while maintaining the same interface.
Ready to continue? Check out our lesson on Method Overriding.
Was this helpful?
Track Your Learning Progress
Sign in to bookmark tutorials and keep track of your learning journey.
Your progress is saved automatically as you read.