Watch Now This tutorial has a related video course created by the Real Python team. Watch it together with the written tutorial to deepen your understanding: Design and Guidance: Object-Oriented Programming in Python
When you build a Python project using object-oriented programming (OOP), planning how the different classes and objects will interact to solve your specific problems is an important part of the job. This planning is known as object-oriented design (OOD), and getting it right can be a challenge. If you’re stuck while designing your Python classes, then the SOLID principles can help you out.
SOLID is a set of five object-oriented design principles that can help you write more maintainable, flexible, and scalable code based on well-designed, cleanly structured classes. These principles are a fundamental part of object-oriented design best practices.
In this tutorial, you’ll:
- Understand the meaning and purpose of each SOLID principle
- Identify Python code that violates some of the SOLID principles
- Apply the SOLID principles to refactor your Python code and improve its design
Throughout your learning journey, you’ll code practical examples to discover how the SOLID principles can lead to well-organized, flexible, maintainable, and scalable code.
To get the most out of this tutorial, you must have a good understanding of Python object-oriented programming concepts, such as classes, interfaces, and inheritance.
Free Bonus: Click here to download sample code so you can build clean, maintainable classes with the SOLID Principles in Python.
Object-Oriented Design in Python: The SOLID Principles
When it comes to writing classes and designing their interactions in Python, you can follow a series of principles that will help you build better object-oriented code. One of the most popular and widely accepted sets of standards for object-oriented design (OOD) is known as the SOLID principles.
If you’re coming from C++ or Java, you may already be familiar with these principles. Maybe you’re wondering if the SOLID principles also apply to Python code. To that question, the answer is a resounding yes. If you’re writing object-oriented code, then you should consider applying these principles to your OOD.
But what are these SOLID principles? SOLID is an acronym that groups five core principles that apply to object-oriented design. These principles are the following:
- Single-responsibility principle (SRP)
- Open–closed principle (OCP)
- Liskov substitution principle (LSP)
- Interface segregation principle (ISP)
- Dependency inversion principle (DIP)
You’ll explore each of these principles in detail and code real-world examples of how to apply them in Python. In the process, you’ll gain a strong understanding of how to write more straightforward, organized, scalable, and reusable object-oriented code by applying the SOLID principles. To kick things off, you’ll start with the first principle on the list.
Single-Responsibility Principle (SRP)
The single-responsibility principle (SRP) comes from Robert C. Martin, more commonly known by his nickname Uncle Bob, who’s a well-respected figure in the software engineering world and one of the original signatories of the Agile Manifesto. In fact, he coined the term SOLID.
The single-responsibility principle states that:
A class should have only one reason to change.
This means that a class should have only one responsibility, as expressed through its methods. If a class takes care of more than one task, then you should separate those tasks into separate classes.
Note: You’ll find the SOLID principles worded in various ways out there. In this tutorial, you’ll refer to them following the wording that Uncle Bob uses in his book Agile Software Development: Principles, Patterns, and Practices. So, all the direct quotes come from this book.
If you want to read alternate wordings in a quick roundup of these and related principles, then check out Uncle Bob’s The Principles of OOD.
This principle is closely related to the concept of separation of concerns, which suggests that you should split your programs into different sections. Each section must address a separate concern.
To illustrate the single-responsibility principle and how it can help you improve your object-oriented design, say that you have the following FileManager class:
# file_manager_srp.py
from pathlib import Path
from zipfile import ZipFile
class FileManager:
def __init__(self, filename):
self.path = Path(filename)
def read(self, encoding="utf-8"):
return self.path.read_text(encoding)
def write(self, data, encoding="utf-8"):
self.path.write_text(data, encoding)
def compress(self):
with ZipFile(self.path.with_suffix(".zip"), mode="w") as archive:
archive.write(self.path)
def decompress(self):
with ZipFile(self.path.with_suffix(".zip"), mode="r") as archive:
archive.extractall()
In this example, your FileManager class has two different responsibilities. It uses the .read() and .write() methods to manage the file. It also deals with ZIP archives by providing the .compress() and .decompress() methods.
This class violates the single-responsibility principle because it has two reasons for changing its internal implementation. To fix this issue and make your design more robust, you can split the class into two smaller, more focused classes, each with its own specific concern:
# file_manager_srp.py
from pathlib import Path
from zipfile import ZipFile
class FileManager:
def __init__(self, filename):
self.path = Path(filename)
def read(self, encoding="utf-8"):
return self.path.read_text(encoding)
def write(self, data, encoding="utf-8"):
self.path.write_text(data, encoding)
class ZipFileManager:
def __init__(self, filename):
self.path = Path(filename)
def compress(self):
with ZipFile(self.path.with_suffix(".zip"), mode="w") as archive:
archive.write(self.path)
def decompress(self):
with ZipFile(