advanced
Step 15 of 20
OOP - Inheritance
Python Programming
OOP - Inheritance
Inheritance is a fundamental OOP concept that allows you to create new classes based on existing ones. The new class (child/subclass) inherits attributes and methods from the existing class (parent/superclass) and can add new functionality or override existing behavior. Inheritance promotes code reuse, establishes clear relationships between types, and enables polymorphism — the ability to treat objects of different classes through a common interface. Python supports single and multiple inheritance with a well-defined method resolution order.
Basic Inheritance
class Animal:
def __init__(self, name, sound):
self.name = name
self.sound = sound
def speak(self):
return f"{self.name} says {self.sound}!"
def __str__(self):
return f"Animal({self.name})"
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name, "Woof") # Call parent constructor
self.breed = breed
def fetch(self, item):
return f"{self.name} fetches the {item}!"
class Cat(Animal):
def __init__(self, name, indoor=True):
super().__init__(name, "Meow")
self.indoor = indoor
def purr(self):
return f"{self.name} is purring..."
# Usage
dog = Dog("Buddy", "Golden Retriever")
cat = Cat("Whiskers")
print(dog.speak()) # "Buddy says Woof!" — inherited
print(dog.fetch("ball")) # "Buddy fetches the ball!" — Dog-specific
print(cat.speak()) # "Whiskers says Meow!" — inherited
print(cat.purr()) # "Whiskers is purring..." — Cat-specific
# isinstance and issubclass
print(isinstance(dog, Dog)) # True
print(isinstance(dog, Animal)) # True
print(issubclass(Dog, Animal)) # True
Method Overriding
class Shape:
def __init__(self, color="black"):
self.color = color
def area(self):
raise NotImplementedError("Subclasses must implement area()")
def describe(self):
return f"A {self.color} shape with area {self.area():.2f}"
class Circle(Shape):
def __init__(self, radius, color="black"):
super().__init__(color)
self.radius = radius
def area(self):
import math
return math.pi * self.radius ** 2
class Rectangle(Shape):
def __init__(self, width, height, color="black"):
super().__init__(color)
self.width = width
self.height = height
def area(self):
return self.width * self.height
class Square(Rectangle):
def __init__(self, side, color="black"):
super().__init__(side, side, color)
# Polymorphism — same interface, different behavior
shapes = [
Circle(5, "red"),
Rectangle(4, 6, "blue"),
Square(3, "green")
]
for shape in shapes:
print(shape.describe())
# "A red shape with area 78.54"
# "A blue shape with area 24.00"
# "A green shape with area 9.00"
Multiple Inheritance and MRO
class Flyable:
def fly(self):
return f"{self.name} is flying!"
class Swimmable:
def swim(self):
return f"{self.name} is swimming!"
class Duck(Animal, Flyable, Swimmable):
def __init__(self, name):
super().__init__(name, "Quack")
duck = Duck("Donald")
print(duck.speak()) # "Donald says Quack!"
print(duck.fly()) # "Donald is flying!"
print(duck.swim()) # "Donald is swimming!"
# Method Resolution Order (MRO)
print(Duck.__mro__)
# (, , , , )
# Mixin pattern — small classes that add specific capabilities
class JSONMixin:
def to_json(self):
import json
return json.dumps(self.__dict__, default=str)
class LogMixin:
def log(self, message):
print(f"[{self.__class__.__name__}] {message}")
class User(JSONMixin, LogMixin):
def __init__(self, name, email):
self.name = name
self.email = email
user = User("Alice", "alice@example.com")
print(user.to_json()) # {"name": "Alice", "email": "alice@example.com"}
user.log("User created") # [User] User created
Abstract Base Classes
from abc import ABC, abstractmethod
class Database(ABC):
@abstractmethod
def connect(self):
pass
@abstractmethod
def execute(self, query):
pass
def close(self):
print("Connection closed") # Concrete method — can be inherited
class SQLiteDB(Database):
def connect(self):
print("Connected to SQLite")
return self
def execute(self, query):
print(f"SQLite executing: {query}")
return []
class PostgresDB(Database):
def connect(self):
print("Connected to PostgreSQL")
return self
def execute(self, query):
print(f"Postgres executing: {query}")
return []
# db = Database() # TypeError — cannot instantiate abstract class
db = SQLiteDB()
db.connect()
db.execute("SELECT * FROM users")
db.close()
Pro tip: Favor composition over inheritance when possible. Instead of creating deep class hierarchies, compose objects by including instances of other classes as attributes. Use inheritance for genuine "is-a" relationships and mixins for adding capabilities. Deep inheritance chains become hard to understand and maintain.
Key Takeaways
- Inheritance lets child classes reuse and extend parent class code; use
super()to call parent methods. - Method overriding allows subclasses to provide specialized implementations of inherited methods.
- Polymorphism means different classes can be used interchangeably through a common interface.
- Python supports multiple inheritance; use the MRO (
Class.__mro__) to understand method resolution order. - Use Abstract Base Classes (ABC) to define interfaces that subclasses must implement.