SOLID Principles in Python
The SOLID principles are a set of design principles in object-oriented programming that help developers create flexible, maintainable, and scalable software. Here’s an overview of the SOLID principles in the context of Python:
S: Single Responsibility Principle (SRP)
- Definition: A class should have one, and only one, reason to change. In other words, it should have a single responsibility.
- In Python: This means designing classes or functions that focus on a single task or purpose.
Example:
class ReportGenerator:
def generate_report(self):
print("Generating report...")
class EmailSender:
def send_email(self):
print("Sending email...")
# Each class has a single responsibility
O: Open/Closed Principle (OCP)
- Definition: Software entities (classes, functions, modules) should be open for extension but closed for modification.
- In Python: Use inheritance or composition to extend functionality without changing existing code.
Example:
class Notification:
def send(self):
pass
class EmailNotification(Notification):
def send(self):
print("Sending email notification...")
class SMSNotification(Notification):
def send(self):
print("Sending SMS notification...")
# New types of notifications can be added without modifying existing code
L: Liskov Substitution Principle (LSP)
- Definition: Subtypes must be substitutable for their base types without altering the correctness of the program.
- In Python: Ensure that subclasses override methods without changing their expected behavior.
Example:
class Bird:
def fly(self):
print("Flying...")
class Sparrow(Bird):
def fly(self):
print("Sparrow flying...")
# Subclass behavior is consistent with the base class
I: Interface Segregation Principle (ISP)
- Definition: A class should not be forced to implement interfaces it does not use. Instead, break down large interfaces into smaller, more specific ones.
- In Python: This can be achieved through multiple small, targeted methods or abstract base classes.
Example:
class Printer:
def print_document(self):
pass
class Scanner:
def scan_document(self):
pass
class MultiFunctionPrinter(Printer, Scanner):
def print_document(self):
print("Printing document...")
def scan_document(self):
print("Scanning document...")
# Separate interfaces for print and scan functionality
D: Dependency Inversion Principle (DIP)
- Definition: High-level modules should not depend on low-level modules. Both should depend on abstractions.
- In Python: Use dependency injection or abstractions to decouple high-level and low-level components.
Example:
class NotificationSender:
def __init__(self, notifier):
self.notifier = notifier
def send(self):
self.notifier.send()
class EmailNotifier:
def send(self):
print("Sending email...")
class SMSNotifier:
def send(self):
print("Sending SMS...")
# Dependency Injection
notifier = EmailNotifier()
sender = NotificationSender(notifier)
sender.send()
By following the SOLID principles in Python, you can write clean, modular, and scalable code.
Interview angle
- “Which SOLID principle matters most in Python?” - dependency inversion and single responsibility. Interface segregation is largely handled by duck typing and Protocols, and open/closed is less pressing in a dynamic language where you can compose behaviour freely.
- “Give a real example of the single responsibility principle.” - a class that both computes a result and persists it has two reasons to change. Separate the calculation from the repository and each becomes independently testable.
- “What does Liskov substitution actually forbid?” - a subclass that strengthens preconditions or weakens postconditions. The practical test: if callers need to know which subclass they hold, substitutability is broken and you should have used composition.
- “Can SOLID be overapplied?” - yes. An interface per class, a factory per interface and a layer per concern produces indirection with no benefit. Apply a principle when you feel the pain it addresses.