ML Algorithms
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 + 32

4. 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 = templates

5. 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**2

6. 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.