Python OOP Complete Guide 2026 — Object-Oriented Programming Mastery
Master Python object-oriented programming from basics to advanced. Classes, inheritance, polymorphism, SOLID principles, dataclasses — everything you need to write professional Python.
Get more content like this on Telegram!
Daily AI tips, notes & resources — free
Python OOP Complete Guide 2026 — Classes, Inheritance, and Beyond
Object-oriented programming is one of those concepts that feels abstract until suddenly it doesn't. Then you wonder how you ever organized code any other way.
OOP is about modeling your problem using objects — things that have both data (attributes) and behavior (methods). A BankAccount has a balance and can deposit/withdraw. A User has a name and email and can authenticate. Organizing code around these objects makes complex systems understandable.
This guide takes you from writing your first class to applying SOLID principles and using Python's most powerful OOP tools.
Why OOP?
# Without OOP — functions and data are unrelated
name = "Alice"
balance = 1000.0
transaction_history = []
def deposit(balance, amount):
return balance + amount
def withdraw(balance, amount):
if amount > balance:
raise ValueError("Insufficient funds")
return balance - amount
# Problems: all these variables are global, unrelated,
# and you can't have two accounts without duplicating everything
# With OOP — data and behavior live together
class BankAccount:
def __init__(self, owner: str, initial_balance: float = 0):
self.owner = owner
self._balance = initial_balance
self._history = []
def deposit(self, amount: float) -> None:
self._balance += amount
self._history.append(f"+${amount:.2f}")
def withdraw(self, amount: float) -> None:
if amount > self._balance:
raise ValueError("Insufficient funds")
self._balance -= amount
self._history.append(f"-${amount:.2f}")
@property
def balance(self) -> float:
return self._balance
# Now you can have many accounts, each with its own state
alice = BankAccount("Alice", 1000)
bob = BankAccount("Bob", 500)
The Four Pillars of OOP
1. Encapsulation — Bundle Data and Methods
class Temperature:
"""Temperature with automatic unit conversion."""
def __init__(self, celsius: float):
self._celsius = celsius # Private by convention (single underscore)
@property
def celsius(self) -> float:
return self._celsius
@celsius.setter
def celsius(self, value: float) -> None:
if value < -273.15:
raise ValueError(f"Temperature below absolute zero: {value}°C")
self._celsius = value
@property
def fahrenheit(self) -> float:
return self._celsius * 9/5 + 32
@property
def kelvin(self) -> float:
return self._celsius + 273.15
def __repr__(self) -> str:
return f"Temperature({self._celsius}°C)"
temp = Temperature(100)
print(temp.celsius) # 100
print(temp.fahrenheit) # 212.0
print(temp.kelvin) # 373.15
temp.celsius = -300 # Raises ValueError!
@property turns a method into an attribute-style access. Setters validate data before accepting it — this is encapsulation in action.
2. Inheritance — Reuse and Extend
class Animal:
def __init__(self, name: str, species: str):
self.name = name
self.species = species
def speak(self) -> str:
raise NotImplementedError("Subclasses must implement speak()")
def describe(self) -> str:
return f"{self.name} is a {self.species}"
def __str__(self) -> str:
return f"{self.species}({self.name})"
class Dog(Animal):
def __init__(self, name: str, breed: str):
super().__init__(name, "Dog") # Call parent __init__
self.breed = breed
def speak(self) -> str:
return f"{self.name} says: Woof!"
def fetch(self, item: str) -> str:
return f"{self.name} fetches the {item}!"
class Cat(Animal):
def __init__(self, name: str, indoor: bool = True):
super().__init__(name, "Cat")
self.indoor = indoor
def speak(self) -> str:
return f"{self.name} says: Meow!"
# Using the hierarchy
animals = [Dog("Rex", "Labrador"), Cat("Whiskers"), Dog("Buddy", "Beagle")]
for animal in animals:
print(animal.speak()) # Polymorphism in action
print(animal.describe())
3. Polymorphism — One Interface, Many Forms
from abc import ABC, abstractmethod
from typing import List
class Shape(ABC):
@abstractmethod
def area(self) -> float: ...
@abstractmethod
def perimeter(self) -> float: ...
def describe(self) -> str:
return f"{type(self).__name__}: area={self.area():.2f}, perimeter={self.perimeter():.2f}"
class Circle(Shape):
def __init__(self, radius: float):
self.radius = radius
def area(self) -> float:
import math
return math.pi * self.radius ** 2
def perimeter(self) -> float:
import math
return 2 * math.pi * self.radius
class Rectangle(Shape):
def __init__(self, width: float, height: float):
self.width = width
self.height = height
def area(self) -> float:
return self.width * self.height
def perimeter(self) -> float:
return 2 * (self.width + self.height)
class Triangle(Shape):
def __init__(self, a: float, b: float, c: float):
self.a, self.b, self.c = a, b, c
def area(self) -> float:
s = (self.a + self.b + self.c) / 2
return (s * (s-self.a) * (s-self.b) * (s-self.c)) ** 0.5
def perimeter(self) -> float:
return self.a + self.b + self.c
# All shapes respond to the same interface
shapes: List[Shape] = [Circle(5), Rectangle(4, 6), Triangle(3, 4, 5)]
total_area = sum(s.area() for s in shapes)
for shape in shapes:
print(shape.describe())
print(f"Total area: {total_area:.2f}")
4. Abstraction — Hide Complexity
Abstract base classes (ABC) define an interface without implementation. Subclasses must implement abstract methods — the ABC enforces the contract.
from abc import ABC, abstractmethod
class PaymentProcessor(ABC):
@abstractmethod
def charge(self, amount: float, customer_id: str) -> str: ...
@abstractmethod
def refund(self, transaction_id: str) -> bool: ...
class StripeProcessor(PaymentProcessor):
def charge(self, amount: float, customer_id: str) -> str:
# Real Stripe API call here
return f"stripe_txn_{customer_id}_{amount}"
def refund(self, transaction_id: str) -> bool:
# Real Stripe refund call here
return True
class PayPalProcessor(PaymentProcessor):
def charge(self, amount: float, customer_id: str) -> str:
return f"paypal_txn_{customer_id}_{amount}"
def refund(self, transaction_id: str) -> bool:
return True
# Your business logic works with ANY payment processor
def process_order(processor: PaymentProcessor, amount: float, user_id: str) -> str:
return processor.charge(amount, user_id)
Dataclasses — Modern Python OOP
Python 3.7+ dataclasses reduce boilerplate significantly:
from dataclasses import dataclass, field
from datetime import datetime
from typing import Optional, List
@dataclass
class User:
name: str
email: str
age: int
created_at: datetime = field(default_factory=datetime.utcnow)
tags: List[str] = field(default_factory=list)
is_active: bool = True
def __post_init__(self):
# Validation after __init__
if self.age < 0:
raise ValueError(f"Age cannot be negative: {self.age}")
if "@" not in self.email:
raise ValueError(f"Invalid email: {self.email}")
self.email = self.email.lower()
@dataclass(frozen=True) # Immutable dataclass
class Point:
x: float
y: float
def distance_to(self, other: "Point") -> float:
return ((self.x - other.x)**2 + (self.y - other.y)**2) ** 0.5
# Usage
user = User("Alice", "Alice@Example.com", 30)
print(user) # User(name='Alice', email='alice@example.com', ...)
p1, p2 = Point(0, 0), Point(3, 4)
print(p1.distance_to(p2)) # 5.0
Dataclasses auto-generate __init__, __repr__, __eq__, and more.
Magic Methods (Dunder Methods)
class Vector:
def __init__(self, x: float, y: float):
self.x = x
self.y = y
def __repr__(self) -> str:
return f"Vector({self.x}, {self.y})"
def __add__(self, other: "Vector") -> "Vector":
return Vector(self.x + other.x, self.y + other.y)
def __sub__(self, other: "Vector") -> "Vector":
return Vector(self.x - other.x, self.y - other.y)
def __mul__(self, scalar: float) -> "Vector":
return Vector(self.x * scalar, self.y * scalar)
def __len__(self) -> int:
return 2 # Always 2D
def __abs__(self) -> float:
return (self.x**2 + self.y**2) ** 0.5
def __eq__(self, other) -> bool:
if not isinstance(other, Vector):
return NotImplemented
return self.x == other.x and self.y == other.y
v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2) # Vector(4, 6)
print(v2 - v1) # Vector(2, 2)
print(abs(v2)) # 5.0
print(v1 * 3) # Vector(3, 6)
print(v1 == Vector(1, 2)) # True
SOLID Principles in Python
| Principle | Meaning | How to Apply |
|---|---|---|
| Single Responsibility | One class, one job | If a class does 3 things, split it into 3 classes |
| Open/Closed | Open for extension, closed for modification | Use inheritance/composition instead of editing classes |
| Liskov Substitution | Subclasses should replace their parent | Don't override methods in ways that break the parent's contract |
| Interface Segregation | Many small interfaces > one big one | Split large ABCs into focused ones |
| Dependency Inversion | Depend on abstractions, not concrete classes | Inject dependencies, use ABCs |
# Single Responsibility Principle
# BAD — User class does too many things
class UserBad:
def save_to_db(self): ...
def send_welcome_email(self): ...
def generate_pdf_report(self): ...
def authenticate(self): ...
# GOOD — each class has one responsibility
class User:
def authenticate(self): ...
class UserRepository:
def save(self, user: User): ...
def find_by_id(self, user_id: int) -> User: ...
class EmailService:
def send_welcome(self, user: User): ...
class ReportGenerator:
def user_report_pdf(self, user: User) -> bytes: ...
Practical OOP: Building a Mini E-commerce System
from dataclasses import dataclass, field
from typing import List
from abc import ABC, abstractmethod
from datetime import datetime
@dataclass
class Product:
id: int
name: str
price: float
stock: int
def is_available(self) -> bool:
return self.stock > 0
@dataclass
class OrderItem:
product: Product
quantity: int
@property
def subtotal(self) -> float:
return self.product.price * self.quantity
@dataclass
class Order:
customer_id: int
items: List[OrderItem] = field(default_factory=list)
created_at: datetime = field(default_factory=datetime.utcnow)
status: str = "pending"
@property
def total(self) -> float:
return sum(item.subtotal for item in self.items)
def add_item(self, product: Product, quantity: int = 1) -> None:
if not product.is_available():
raise ValueError(f"{product.name} is out of stock")
if product.stock < quantity:
raise ValueError(f"Only {product.stock} units available")
self.items.append(OrderItem(product=product, quantity=quantity))
# Usage
laptop = Product(1, "MacBook Pro", 2499.99, 5)
mouse = Product(2, "Magic Mouse", 79.99, 20)
order = Order(customer_id=42)
order.add_item(laptop, quantity=1)
order.add_item(mouse, quantity=2)
print(f"Order total: ${order.total:.2f}") # $2659.97
OOP shines when systems grow complex. For applying these patterns in a web context, see the FastAPI tutorial — Pydantic models are a form of dataclasses on steroids. For understanding more advanced Python patterns built on top of OOP, read the decorators and generators guide.
The best way to internalize OOP is to redesign something you have already built using classes. Take a messy script with many global variables and refactor it. The clarity you gain will change how you write Python forever.
Python OOP design patterns and SOLID principle examples available in the AiTechWorlds Telegram channel!
Frequently Asked Questions
AiTechWorlds Team
✓ Verified WriterThe AiTechWorlds team is passionate about AI, technology, and education. We create high-quality, research-backed content to help you learn, grow, and succeed in the modern digital world.
Related Articles
Python Async Programming Guide 2026 — asyncio, aiohttp & Concurrency
Master async programming in Python with asyncio. Learn concurrent programming, aiohttp for async HTTP, async database operations, and build high-performance Python applications.
Python Error Handling & Debugging 2026 — Write Bulletproof Code
Master Python error handling and debugging techniques. Learn try/except, custom exceptions, logging, pdb, and professional debugging strategies to write robust Python code.
Python Decorators and Generators — Advanced Python Made Simple 2026
Master Python decorators and generators — two of Python's most powerful features. Clear explanations, real-world examples, and practical patterns you'll actually use.
Python for Machine Learning 2026 — Your First ML Project with scikit-learn
Start your machine learning journey with Python and scikit-learn. Build real ML models, understand the ML workflow, and go from raw data to predictions — complete beginner guide.