Python OOP¶
Object-oriented Python for modeling data and systems: classes, objects, methods, inheritance, encapsulation, abstract classes, dataclasses, properties, and magic methods.
Table of Contents¶
- Installation / Setup
- Core Syntax
- Common Operations
- Practical Examples
- Common Patterns
- Best Practices
- Common Mistakes
- Troubleshooting
- Related Cheat Sheets
- Official Documentation
Installation / Setup¶
OOP is built into Python — no install needed. Run with Python 3.10+ for the newest syntax (dataclass(slots=True), etc.).
Core Syntax¶
Classes and objects¶
class Model:
def __init__(self, name: str, lr: float = 0.01):
self.name = name # instance attribute
self.lr = lr
self.trained = False
m = Model("linear", lr=0.1) # __init__ runs; m is the object
m.trained = True # attributes are dynamic by default
Instance, class, and static methods¶
| Method type | Decorator | First arg | Use for |
|---|---|---|---|
| Instance | none | self |
Work with object state |
| Class | @classmethod |
cls |
Factory constructors, class-level state |
| Static | @staticmethod |
— | Utility tied to the class |
class Dataset:
total = 0 # class attribute (shared)
def __init__(self, rows):
self.rows = rows
Dataset.total += len(rows)
def shape(self): # instance method
return (len(self.rows), len(self.rows[0]))
@classmethod
def from_csv(cls, path): # factory
import csv
with open(path) as f:
return cls(list(csv.reader(f)))
@staticmethod
def is_valid(rows): # utility
return bool(rows) and all(len(r) == len(rows[0]) for r in rows)
Inheritance¶
class BaseModel:
def __init__(self, name):
self.name = name
def predict(self, X): # overridden below
raise NotImplementedError
class LinearModel(BaseModel):
def __init__(self, name="linear", lr=0.01):
super().__init__(name) # call parent __init__
self.lr = lr
def predict(self, X):
return X @ self.weights # polymorphism: same interface, new behavior
issubclass(LinearModel, BaseModel) # True
super()resolves the next class in the MRO (ClassName.__mro__).- Multiple inheritance works via C3 linearization — keep it shallow.
Polymorphism¶
Duck typing: an object's behavior matters, not its declared type.
def summarize(x):
return x.mean() # works for any object with .mean()
import numpy as np
import pandas as pd
summarize(np.array([1, 2, 3])) # numpy array
summarize(pd.Series([1, 2, 3])) # pandas Series
Encapsulation¶
class Account:
def __init__(self):
self._internal = 1 # convention: private by convention
self.__mangled = 2 # name-mangled to _Account__mangled
def get_balance(self): # public API
return self._internal
_x = "please don't touch" (convention). __x = name-mangled to deter accidental access. Real encapsulation comes from properties.
Abstract classes¶
from abc import ABC, abstractmethod
class Evaluator(ABC):
@abstractmethod
def score(self, y_true, y_pred):
"""Must be implemented by subclasses."""
class Accuracy(Evaluator):
def score(self, y_true, y_pred):
return sum(a == b for a, b in zip(y_true, y_pred)) / len(y_true)
# Evaluator() -> TypeError: abstract class cannot be instantiated
Dataclasses¶
from dataclasses import dataclass, field, asdict
@dataclass
class Config:
lr: float = 0.01
epochs: int = 10
layers: list[int] = field(default_factory=list) # no mutable default!
name: str = field(default="model", repr=False)
cfg = Config(epochs=20)
cfg.lr # 0.01
asdict(cfg) # {'lr': 0.01, 'epochs': 20, 'layers': [], 'name': 'model'}
@dataclass auto-generates __init__, __repr__, __eq__. Use frozen=True for immutability, order=True for comparison operators, slots=True (3.10+) for memory efficiency.
Properties¶
class Temperature:
def __init__(self, celsius):
self._celsius = celsius
@property
def celsius(self):
return self._celsius
@property
def fahrenheit(self): # computed, read-only
return self._celsius * 9 / 5 + 32
@fahrenheit.setter
def fahrenheit(self, value): # validated setter
if value < -459.67:
raise ValueError("below absolute zero")
self._celsius = (value - 32) * 5 / 9
t = Temperature(25)
t.fahrenheit # 77.0
t.fahrenheit = 212 # setter runs, validation applies
Magic methods¶
| Method | Called when | Purpose |
|---|---|---|
__init__(self, ...) |
Class(...) |
Initialize instance |
__repr__ / __str__ |
repr(x) / str(x) |
Developer / user-facing string |
__len__ |
len(x) |
Size |
__getitem__ / __setitem__ |
x[i], x[i] = v |
Subscription |
__iter__ / __next__ |
iter(x), for i in x |
Iteration |
__eq__ / __lt__ / __gt__ |
==, <, > |
Comparison |
__hash__ |
hash(x) |
Dict/set membership |
__call__ |
x(...) |
Make object callable |
__enter__ / __exit__ |
with x: |
Context manager |
__add__ / __mul__ |
x + y, x * y |
Operators |
__bool__ |
bool(x), if x |
Truthiness |
class SparseVector:
def __init__(self, data): self.data = dict(data)
def __getitem__(self, i): return self.data.get(i, 0.0)
def __len__(self): return max(self.data, default=-1) + 1
def __repr__(self): return f"SparseVector({self.data})"
Practical Examples¶
Model wrapper with dataclass + property¶
from dataclasses import dataclass
import numpy as np
@dataclass
class LinearRegression:
lr: float = 0.01
epochs: int = 100
weights: np.ndarray | None = None
def fit(self, X, y):
X = np.c_[np.ones(len(X)), X] # bias column
self.weights = np.linalg.pinv(X.T @ X) @ X.T @ y
return self
def predict(self, X):
X = np.c_[np.ones(len(X)), X]
return X @ self.weights
model = LinearRegression(epochs=500).fit(X, y)
Callable metric¶
class Accuracy:
def __call__(self, y_true, y_pred):
return float(sum(a == b for a, b in zip(y_true, y_pred))) / len(y_true)
acc = Accuracy()
acc(y_true, y_pred) # objects that behave like functions
Common Patterns¶
# Factory via classmethod (also see dataclass + classmethod)
@dataclass
class Dataset:
path: str
data: list = field(default_factory=list)
@classmethod
def load(cls, path):
return cls(path, data=read_file(path))
# Composition over inheritance: give objects collaborators
class Pipeline:
def __init__(self, steps: list):
self.steps = steps
def run(self, x):
for s in self.steps:
x = s(x)
return x
# __slots__ to cut memory for millions of small objects
class Point:
__slots__ = ("x", "y")
def __init__(self, x, y): self.x, self.y = x, y
Best Practices¶
- Prefer
@dataclassfor data containers — less boilerplate than hand-written__init__/__repr__. - Use
super().__init__(...)in subclasses — don't re-implement parent setup. - Keep inheritance shallow (1–2 levels); prefer composition.
- Use
@propertyinstead of bareget_/set_methods. - Use abstract base classes to define interfaces for pluggable components (models, metrics, loaders).
- Design for duck typing — accept objects with the needed methods, matching how pandas/NumPy interop.
- Never use mutable default arguments, including in dataclasses (
field(default_factory=...)).
Common Mistakes¶
| Mistake | Consequence | Correct approach |
|---|---|---|
def __init__(self, layers=[]) |
Shared list across instances | layers: list = field(default_factory=list) |
Defining __eq__ without __hash__ |
Objects unhashable | Set __hash__ = None intentionally, or use frozen dataclass |
Calling super().__init__ with wrong args |
Attribute errors in parent | Match the parent signature |
| Deep inheritance chains | Fragile, hard to debug | Composition / mixins |
| Mutable class attributes | State shared by all instances | Use instance attributes in __init__ |
Troubleshooting¶
| Symptom | Cause | Fix |
|---|---|---|
TypeError: Can't instantiate abstract class |
Missing @abstractmethod implementations |
Implement all abstract methods |
AttributeError: 'X' object has no attribute 'y' |
Attribute set on instance only / typo | Set it in __init__; check spelling |
TypeError: 'Model' object is not callable |
Called object without __call__ |
Add __call__ or call a real method |
TypeError: unhashable type: 'list' |
List used in dict/set | Convert to tuple |
Dataclass __init__ ignores new fields |
Mutable default without default_factory |
Use field(default_factory=...) |
Related Cheat Sheets¶
- Python Essentials — functions, decorators, modules
- Python Standard Library —
dataclasses,functools,copy - Python Typing — generics and
Protocolfor OOP interfaces - Home · Central Navigation Index
Official Documentation¶
Last Updated¶
2026-09-06 — Initial version.