On this page
article
Object-Oriented Programming
Master Python OOP — classes, objects, inheritance, polymorphism, encapsulation, abstract classes, magic methods, and composition over inheritance.
Object-Oriented Programming (OOP) organizes code around objects that combine data and behavior. Python supports OOP fully while remaining flexible enough for other paradigms.
Classes and Objects
class BankAccount:
def __init__(self, owner, balance=0.0):
self.owner = owner
self.balance = balance
def deposit(self, amount):
if amount <= 0:
raise ValueError("Deposit must be positive")
self.balance += amount
return self.balance
def withdraw(self, amount):
if amount > self.balance:
raise ValueError("Insufficient funds")
self.balance -= amount
return self.balance
def __repr__(self):
return f"BankAccount(owner={self.owner!r}, balance={self.balance})"
account = BankAccount("Alice", 1000)
account.deposit(500)
account.withdraw(200)
print(account) # BankAccount(owner='Alice', balance=1300.0)
__init__— constructor, called on instantiationself— reference to the current instance__repr__— developer-friendly string representation
Instance vs Class Attributes
class Dog:
species = "Canis familiaris" # class attribute — shared
def __init__(self, name):
self.name = name # instance attribute — unique
d1 = Dog("Buddy")
d2 = Dog("Max")
print(d1.species) # Canis familiaris
d1.species = "Wolf" # creates instance attribute, doesn't change class
print(d2.species) # Canis familiaris
Inheritance
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
raise NotImplementedError("Subclass must implement speak()")
class Dog(Animal):
def speak(self):
return f"{self.name} says Woof!"
class Cat(Animal):
def speak(self):
return f"{self.name} says Meow!"
animals = [Dog("Buddy"), Cat("Whiskers")]
for animal in animals:
print(animal.speak()) # polymorphism
super()
class Employee:
def __init__(self, name, salary):
self.name = name
self.salary = salary
class Manager(Employee):
def __init__(self, name, salary, department):
super().__init__(name, salary)
self.department = department
Encapsulation
Python uses naming conventions instead of strict access control:
class Account:
def __init__(self, balance):
self._balance = balance # "protected" — convention
self.__pin = "1234" # name mangled to _Account__pin
@property
def balance(self):
return self._balance
@balance.setter
def balance(self, value):
if value < 0:
raise ValueError("Balance cannot be negative")
self._balance = value
| Convention | Meaning |
|---|---|
name |
Public |
_name |
Internal use (convention) |
__name |
Name mangled (harder to access accidentally) |
Abstract Base Classes
from abc import ABC, abstractmethod
class PaymentProcessor(ABC):
@abstractmethod
def charge(self, amount: float) -> str:
pass
@abstractmethod
def refund(self, transaction_id: str) -> bool:
pass
class StripeProcessor(PaymentProcessor):
def charge(self, amount):
return f"stripe_txn_{amount}"
def refund(self, transaction_id):
return True
# PaymentProcessor() # TypeError — can't instantiate ABC
Magic (Dunder) Methods
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __mul__(self, scalar):
return Vector(self.x * scalar, self.y * scalar)
def __eq__(self, other):
return self.x == other.x and self.y == other.y
def __len__(self):
return 2
def __getitem__(self, index):
return (self.x, self.y)[index]
def __str__(self):
return f"({self.x}, {self.y})"
v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2) # (4, 6)
print(v1 * 3) # (3, 6)
Common magic methods: __str__, __repr__, __eq__, __lt__, __len__, __getitem__, __call__, __enter__/__exit__.
Composition Over Inheritance
Prefer composing objects over deep inheritance hierarchies:
class Engine:
def start(self):
return "Engine running"
class Car:
def __init__(self):
self.engine = Engine() # has-a, not is-a
def start(self):
return self.engine.start()
# vs fragile inheritance:
# class Vehicle: ...
# class Motorized(Vehicle): ...
# class Car(Motorized): ...
dataclasses — Less Boilerplate
from dataclasses import dataclass, field
@dataclass
class Product:
name: str
price: float
tags: list[str] = field(default_factory=list)
@property
def display(self):
return f"{self.name}: ${self.price:.2f}"
See also Advanced Topics for full dataclass coverage.
When to Use OOP
| Use classes when… | Use functions when… |
|---|---|
| Modeling entities with state | Transforming data |
| Multiple related methods on shared state | Single-purpose operations |
| Building frameworks/libraries | Scripts and utilities |
Python doesn’t require everything to be a class — use OOP when it clarifies structure, not by default.