Python
27 / 29
Object-Oriented Python
Classes, inheritance, composition, and the patterns idiomatic Python actually uses.
OOP is a tool, not a religion
Python supports OOP but doesn't force it. Reach for classes when you have state + behavior that belong together. For pure transformations, plain functions are usually cleaner.
1. Classes & Instances
class Account:
def __init__(self, owner: str, balance: float = 0.0):
self.owner = owner
self.balance = balance
def deposit(self, amount: float) -> None:
if amount <= 0:
raise ValueError("amount must be positive")
self.balance += amount
def __repr__(self) -> str:
return f"Account({self.owner!r}, balance={self.balance:.2f})"2. Dataclasses
For value objects (mostly data, little behavior), @dataclassremoves boilerplate — auto-generates __init__,__repr__, and __eq__.
from dataclasses import dataclass, field
@dataclass(frozen=True, slots=True)
class Point:
x: float
y: float
tags: list[str] = field(default_factory=list)3. Properties
Replace getters/setters with clean attribute access while keeping validation:
class Temperature:
def __init__(self, celsius: float): self.celsius = celsius
@property
def celsius(self) -> float: return self._c
@celsius.setter
def celsius(self, v: float) -> None:
if v < -273.15: raise ValueError("below absolute zero")
self._c = v
@property
def fahrenheit(self) -> float: return self._c * 9/5 + 324. Inheritance vs Composition
Inheritance models is-a. Composition models has-a. Composition is more flexible and avoids deep brittle hierarchies — favor it by default.
# Inheritance (is-a)
class Dog(Animal): ...
# Composition (has-a) — usually preferred
class EmailService:
def __init__(self, transport: Transport, templates: TemplateStore):
self.transport = transport
self.templates = templates5. Class vs Static vs Instance Methods
class Pizza:
def __init__(self, toppings): self.toppings = toppings
@classmethod
def margherita(cls): # alternative constructor
return cls(["tomato", "mozzarella", "basil"])
@staticmethod
def area(radius: float) -> float: # no self / cls
return 3.14159 * radius**26. Abstract Base Classes
from abc import ABC, abstractmethod
class Storage(ABC):
@abstractmethod
def save(self, key: str, value: bytes) -> None: ...
@abstractmethod
def load(self, key: str) -> bytes: ...
class S3Storage(Storage):
def save(self, key, value): ...
def load(self, key): ...7. Common Pitfalls
- Mutable default arguments — use
field(default_factory=list). - Class-level mutables are shared across instances.
- Deep inheritance chains make MRO confusing — keep it shallow.
- Don't overuse classes for stateless helpers — a module-level function is fine.