Python Inheritance
Inheritance allows a class to inherit attributes and methods from another class. Learn single inheritance, multiple inheritance, method overriding, and the super() function.
What is Inheritance?
Inheritance is a fundamental concept in object-oriented programming that allows a class (child class) to inherit attributes and methods from another class (parent class). This promotes code reusability and establishes a relationship between classes.
"Inheritance allows child classes to inherit and extend the functionality of parent classes."
Single Inheritance
A child class inherits from one parent class:
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return "Some generic animal sound"
def eat(self):
return f"{self.name} is eating"
class Dog(Animal):
def speak(self):
return f"{self.name} says Woof!"
class Cat(Animal):
def speak(self):
return f"{self.name} says Meow!"
# Creating instances
dog = Dog("Buddy")
cat = Cat("Whiskers")
print(dog.speak()) # Buddy says Woof!
print(cat.speak()) # Whiskers says Meow!
print(dog.eat()) # Buddy is eating
print(cat.eat()) # Whiskers is eating
The super() Function
super() allows access to parent class methods:
class Vehicle:
def __init__(self, make, model):
self.make = make
self.model = model
def get_info(self):
return f"{self.make} {self.model}"
class Car(Vehicle):
def __init__(self, make, model, year, color):
super().__init__(make, model) # Call parent constructor
self.year = year
self.color = color
def get_info(self):
parent_info = super().get_info() # Call parent method
return f"{parent_info} ({self.year}) - {self.color}"
car = Car("Toyota", "Camry", 2020, "Blue")
print(car.get_info()) # Toyota Camry (2020) - Blue
Method Overriding
Child classes can override parent methods:
class Shape:
def area(self):
return 0
def perimeter(self):
return 0
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)
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14159 * self.radius ** 2
def perimeter(self):
return 2 * 3.14159 * self.radius
rect = Rectangle(5, 3)
circle = Circle(4)
print(f"Rectangle area: {rect.area()}") # 15
print(f"Rectangle perimeter: {rect.perimeter()}") # 16
print(f"Circle area: {circle.area()}") # 50.26544
print(f"Circle perimeter: {circle.perimeter()}") # 25.13272
Multiple Inheritance
A class can inherit from multiple parent classes:
class Flyable:
def fly(self):
return "Flying high!"
class Swimmable:
def swim(self):
return "Swimming gracefully!"
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 gracefully!
print(duck.quack()) # Donald says Quack!
Method Resolution Order (MRO)
Python uses C3 linearization for MRO:
class A:
def method(self):
return "Method from A"
class B(A):
def method(self):
return "Method from B"
class C(A):
def method(self):
return "Method from C"
class D(B, C):
pass
d = D()
print(d.method()) # Method from B (B comes first in inheritance)
print(D.__mro__) # (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
Multilevel Inheritance
Inheritance through multiple levels:
class GrandParent:
def __init__(self, name):
self.name = name
def introduce(self):
return f"Hello, I'm {self.name}"
class Parent(GrandParent):
def __init__(self, name, occupation):
super().__init__(name)
self.occupation = occupation
def work(self):
return f"I work as a {self.occupation}"
class Child(Parent):
def __init__(self, name, occupation, hobby):
super().__init__(name, occupation)
self.hobby = hobby
def play(self):
return f"I love {self.hobby}"
child = Child("Alice", "Engineer", "painting")
print(child.introduce()) # Hello, I'm Alice
print(child.work()) # I work as a Engineer
print(child.play()) # I love painting
Hierarchical Inheritance
Multiple child classes inherit from one parent:
class Employee:
def __init__(self, name, salary):
self.name = name
self.salary = salary
def get_info(self):
return f"Name: {self.name}, Salary: ${self.salary}"
class Manager(Employee):
def __init__(self, name, salary, department):
super().__init__(name, salary)
self.department = department
def manage_team(self):
return f"Managing {self.department} department"
class Developer(Employee):
def __init__(self, name, salary, language):
super().__init__(name, salary)
self.language = language
def code(self):
return f"Coding in {self.language}"
class Designer(Employee):
def __init__(self, name, salary, tool):
super().__init__(name, salary)
self.tool = tool
def design(self):
return f"Designing with {self.tool}"
manager = Manager("John", 80000, "IT")
developer = Developer("Jane", 70000, "Python")
designer = Designer("Bob", 65000, "Figma")
print(manager.get_info()) # Name: John, Salary: $80000
print(manager.manage_team()) # Managing IT department
print(developer.code()) # Coding in Python
print(designer.design()) # Designing with Figma
Hybrid Inheritance
Combination of multiple and multilevel inheritance:
class Person:
def __init__(self, name):
self.name = name
class Student(Person):
def __init__(self, name, grade):
super().__init__(name)
self.grade = grade
class Teacher(Person):
def __init__(self, name, subject):
super().__init__(name)
self.subject = subject
class TeachingAssistant(Student, Teacher):
def __init__(self, name, grade, subject, hours):
Student.__init__(self, name, grade)
Teacher.__init__(self, name, subject)
self.hours = hours
def assist(self):
return f"Assisting {self.hours} hours per week"
ta = TeachingAssistant("Mike", "A", "Computer Science", 20)
print(f"Name: {ta.name}") # Name: Mike
print(f"Grade: {ta.grade}") # Grade: A
print(f"Subject: {ta.subject}") # Subject: Computer Science
print(ta.assist()) # Assisting 20 hours per week
Using isinstance() and issubclass()
Check inheritance relationships:
class Animal:
pass
class Dog(Animal):
pass
class Cat(Animal):
pass
dog = Dog()
cat = Cat()
print(isinstance(dog, Dog)) # True
print(isinstance(dog, Animal)) # True
print(isinstance(dog, Cat)) # False
print(issubclass(Dog, Animal)) # True
print(issubclass(Cat, Animal)) # True
print(issubclass(Dog, Cat)) # False
Abstract Base Classes (ABC)
Define interfaces that must be implemented:
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
pass
@abstractmethod
def perimeter(self):
pass
class Square(Shape):
def __init__(self, side):
self.side = side
def area(self):
return self.side ** 2
def perimeter(self):
return 4 * self.side
# This would raise an error if area() or perimeter() weren't implemented
square = Square(5)
print(f"Area: {square.area()}") # 25
print(f"Perimeter: {square.perimeter()}") # 20
Composition vs Inheritance
When to use composition instead of inheritance:
# Inheritance (IS-A relationship)
class Car(Vehicle):
pass # A car IS A vehicle
# Composition (HAS-A relationship)
class Car:
def __init__(self):
self.engine = Engine() # A car HAS AN engine
def start(self):
self.engine.start()
# Better approach for complex relationships
class Team:
def __init__(self):
self.members = [] # Team HAS members
def add_member(self, member):
self.members.append(member)
class Player:
def __init__(self, name):
self.name = name
team = Team()
player = Player("John")
team.add_member(player) # Team has players, not inherits from them
Best Practices
- Use inheritance for IS-A relationships: Dog IS AN Animal
- Use composition for HAS-A relationships: Car HAS AN Engine
- Keep inheritance hierarchies shallow: Avoid deep inheritance chains
- Use super() for proper method resolution: Especially in multiple inheritance
- Override methods appropriately: Maintain Liskov Substitution Principle
- Use abstract base classes for interfaces: Define contracts
- Document inheritance relationships: Use docstrings
- Avoid diamond problem complications: Careful with multiple inheritance
- Prefer composition over inheritance: When in doubt
- Use isinstance() for type checking: Not type() == comparison
Common Patterns
# Template Method Pattern
class Game:
def play(self):
self.initialize()
self.start_play()
self.end_play()
def initialize(self):
pass
def start_play(self):
pass
def end_play(self):
pass
class Chess(Game):
def initialize(self):
print("Setting up chess board")
def start_play(self):
print("Playing chess")
def end_play(self):
print("Chess game finished")
# Factory Method Pattern
class AnimalFactory:
@staticmethod
def create_animal(animal_type, name):
if animal_type == "dog":
return Dog(name)
elif animal_type == "cat":
return Cat(name)
else:
raise ValueError("Unknown animal type")
# Strategy Pattern
class SortStrategy:
def sort(self, data):
pass
class BubbleSort(SortStrategy):
def sort(self, data):
# Bubble sort implementation
return sorted(data)
class QuickSort(SortStrategy):
def sort(self, data):
# Quick sort implementation
return sorted(data)
class Sorter:
def __init__(self, strategy):
self.strategy = strategy
def sort_data(self, data):
return self.strategy.sort(data)
sorter = Sorter(BubbleSort())
print(sorter.sort_data([3, 1, 4, 1, 5])) # [1, 1, 3, 4, 5]
Inheritance is a powerful feature that enables code reuse and establishes relationships between classes. However, it should be used judiciously - composition is often a better choice for complex relationships.