Python OOP Complete Guide 2026 — Object-Oriented Programming Mastery
⚡ Quick Answer
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
Advertisement
Python OOP Complete Guide 2026 — Classes, Inheritance, and Beyond
Object-oriented programming (OOP) models a problem as objects — bundles of data (attributes) and behavior (methods) — the same way a filing cabinet bundles a folder's contents with the label on its tab, instead of scattering papers across the room.
A BankAccount has a balance and can deposit or withdraw. A User has a name and email and can authenticate. Organizing code around these objects, instead of loose functions and global variables, keeps complex systems understandable as they grow.
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
A dataclass is a class that's mostly just data — Python 3.7+'s @dataclass decorator auto-writes the repetitive __init__, __repr__, and __eq__ code so you don't have to.
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.0frozen=True makes a dataclass immutable — attempting p1.x = 5 raises an error, which is exactly what you want for a value like a coordinate that should never mutate after creation.
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)) # TrueSOLID 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.97OOP 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!
Further Reading
- Python for Machine Learning 2026 — Your First ML Project with scikit-learn
- FastAPI Tutorial: Building Your First REST API in 30 Minutes
- The Python Developer's Guide to APIs: Requests Library Deep Dive
- Python Async Programming Guide 2026 — asyncio, aiohttp & Concurrency
- Django vs Flask in 2025: Which Framework Should You Learn?
- How to Use GitHub Copilot to 5x Your Coding Speed
Advertisement
💬 DiscussionPowered by GitHub Discussions
Frequently Asked Questions
Problem Solver and Cloud Expert
Solves complex infrastructure challenges and architects reliable, scalable cloud deployments for every project. Shamshur Rahman keeps AiTechWorlds’ hosting and cloud infrastructure fast, resilient, and cost-efficient.
Not sure yet? Ask AI about this article
Get an instant, unbiased AI summary of “Python OOP Complete Guide 2026 — Object-Oriented Programming Mastery”.
Advertisement
Related Articles
The Python Libraries Every Developer Must Know in 2026
The essential Python libraries for 2026: from requests and pandas to FastAPI and LangChain — what each does, when to use it, and how to get started quickly.
Django vs Flask in 2026: Which Framework Should You Learn?
An honest Django vs Flask comparison for 2026 — which Python framework to learn first, when each excels, and why FastAPI has changed the equation.
FastAPI Tutorial: Building Your First REST API in 30 Minutes
A hands-on FastAPI tutorial for beginners: build a fully functional REST API in 30 minutes with CRUD endpoints, request validation, and automatic docs.
Jupyter Notebook Guide: The Data Scientist's Favorite Tool
A complete Jupyter Notebook guide for 2026: installation, essential shortcuts, best practices, and how data scientists use Jupyter for exploration, analysis, and sharing.