S.O.L.I.D in Python
- Biyi Akinpelu

- Mar 3
- 3 min read
Updated: Mar 4

If you want an OOP-based Python project that can grow without collapsing under its own weight, then the SOLID principles are not optional — they are the discipline that keeps a system understandable, extendable, and testable over time.
These five principles were formulated by Robert C. Martin to make object-oriented systems resilient to change — which is exactly what every real production system faces.
1. S — Single Responsibility Principle (SRP)
A class should have only one reason to change.
In plain terms:
A class must do one job and do it well.
❌ Bad Design
class Report:
def generate(self):
print("Generating Report")
def save_to_file(self):
print("Saving to file")
def send_email(self):
print("Sending email")
This class has 3 responsibilities:
business logic
persistence
communication
Now:
File format changes → modify class
Email provider changes → modify class
Report logic changes → modify class
One class is now fragile.
✅ Good Design
class Report:
def generate(self):
print("Generating Report")
class ReportSaver:
def save(self, report):
print("Saving to file")
class EmailService:
def send(self, report):
print("Sending email")
Each class now has one job.
Now your:
mocks become easy
TDD becomes realistic
FDD becomes incremental
2. O — Open/Closed Principle (OCP)
Software should be open for extension but closed for modification.
Meaning:
You should add new behaviourwithout touching working code.
❌ Bad Design
class Discount:
def calculate(self, customer_type, amount):
if customer_type == "regular":
return amount * 0.1
elif customer_type == "vip":
return amount * 0.2
Adding a new type = modify logic = risk regression.
✅ Good Design
class DiscountStrategy:
def apply(self, amount):
pass
class RegularDiscount(DiscountStrategy):
def apply(self, amount):
return amount * 0.1
class VIPDiscount(DiscountStrategy):
def apply(self, amount):
return amount * 0.2
Now new feature:
class SuperVIPDiscount(DiscountStrategy):
def apply(self, amount):
return amount * 0.3
No existing code touched.
Perfect for Feature Driven Development.
3. L — Liskov Substitution Principle (LSP)
Subclasses must be replaceable for their base class.
❌ Violation
class Bird:
def fly(self):
pass
class Penguin(Bird):
def fly(self):
raise Exception("Cannot fly")
Now anywhere expecting Bird breaks.
✅ Fix
class Bird:
pass
class FlyingBird(Bird):
def fly(self):
pass
class Sparrow(FlyingBird):
pass
class Penguin(Bird):
pass
No broken assumptions.
Inheritance must honour behaviour.
4. I — Interface Segregation Principle (ISP)
Don’t force classes to depend on methods they don’t use.
❌ Bad
class Worker:
def work(self):
pass
def eat(self):
pass
Robot must now implement eat().
✅ Good
class Workable:
def work(self):
pass
class Eatable:
def eat(self):
pass
class Human(Workable, Eatable):
pass
class Robot(Workable):
pass
Lean interfaces make:
mocking easier
testing faster
coupling lower
5. D — Dependency Inversion Principle (DIP)
Depend on abstractions, not concrete classes.
❌ Bad
class MySQLDatabase:
def save(self):
pass
class UserService:
def __init__(self):
self.db = MySQLDatabase()
Now:
You cannot test without real DB.
✅ Good
class Database:
def save(self):
pass
class MySQLDatabase(Database):
def save(self):
pass
class UserService:
def __init__(self, db: Database):
self.db = db
Now test:
mock_db = Mock()
service = UserService(mock_db)
Mocks slide in naturally.
Why SOLID Matters in Python Projects
Applying SOLID enables:
✔ true unit testing
✔ clean mocking in TDD
✔ safe refactoring
✔ plug-in architecture
✔ parallel FDD feature delivery
✔ legacy system rescue
Without SOLID:
every new feature modifies old code every modification risks regression every regression slows delivery
With SOLID:
new behaviour is added existing behaviour remains stable
The Real Outcome
SOLID transforms your system from:
tightly coupled implementation
into:
behaviour-driven architecture
— where features can evolve one responsibility at a time.



Comments