OOP Basics
10 examples to get you started with object-oriented Python - 7 basic and 3 intermediate.
Prerequisites
- Python 3.14.0 and Python Basics.
Basic Examples
1. Define a Class and init
Initialize instance state in the constructor.
class Greeter:
def __init__(self, prefix: str) -> None:
self.prefix = prefix
def greet(self, name: str) -> str:
return f"{self.prefix}, {name}!"
g = Greeter("Hello")
print(g.greet("Ada"))__init__configures new instances - not a true constructor in C++ sense.selfis convention for the instance reference.- Methods are functions bound to instances.
Related: Dunder / Magic Methods - special methods
2. Instance vs Class Attributes
Class attributes are shared; instance attributes are per-object.
class Counter:
total = 0
def __init__(self) -> None:
Counter.total += 1
self.value = 0- Mutating
Counter.totalaffects all instances unless shadowed on instance. - Instance attrs set in
__init__are independent per object. - Avoid mutable class attributes (
list,dict) as shared state traps.
Related: Class vs Instance vs Static Methods - @classmethod
3. Simple Inheritance
Reuse behavior by subclassing.
class Animal:
def speak(self) -> str:
return "..."
class Dog(Animal):
def speak(self) -> str:
return "woof"- Subclasses override methods by defining same name.
super()calls parent implementation - critical in__init__chains.- Prefer composition when relationship is not "is-a".
Related: Inheritance & MRO - multiple inheritance
4. dataclass for Records
Reduce boilerplate for data-holding classes.
from dataclasses import dataclass
@dataclass
class Point:
x: int
y: int
p = Point(3, 4)
print(p.x, p.y)- Auto-generates
__init__,__repr__, and comparisons when configured. - Add
frozen=Truefor immutable value objects. - Use
field(default_factory=list)for mutable defaults.
Related: dataclasses - field and post_init
5. Properties for Encapsulation
Expose attributes with getters/setters without changing call sites.
class Account:
def __init__(self, balance: float) -> None:
self._balance = balance
@property
def balance(self) -> float:
return self._balance- Leading
_signals internal by convention - not enforced privacy. @propertycan add validation in setter later.- Keeps public API attribute-like while allowing logic.
Related: Properties & Descriptors - descriptors
6. repr for Debugging
Readable object representation in logs and REPL.
class User:
def __init__(self, user_id: int, name: str) -> None:
self.user_id = user_id
self.name = name
def __repr__(self) -> str:
return f"User({self.user_id!r}, {self.name!r})"__repr__should ideally be unambiguous and reconstruct-friendly.__str__is for human display;printuses__str__falling back to__repr__.- Dataclasses generate useful
__repr__automatically.
Related: Dunder / Magic Methods - repr vs str
7. Enum for Named Constants
Replace magic strings with typed enumerations.
from enum import StrEnum
class Role(StrEnum):
ADMIN = "admin"
DEV = "dev"
def authorize(role: Role) -> bool:
return role == Role.ADMINStrEnummembers compare cleanly to strings in JSON contexts.- Enums are singletons - identity stable for comparisons.
- Exhaustive
matchon enum members catches missing cases.
Related: Enums - IntEnum and flags
Intermediate Examples
8. Composition Over Inheritance
Delegate behavior to helper objects.
class EmailSender:
def send(self, to: str, body: str) -> None:
print(f"to={to} body={body}")
class Notifier:
def __init__(self, sender: EmailSender) -> None:
self._sender = sender
def notify(self, user_email: str, message: str) -> None:
self._sender.send(user_email, message)Notifierhas-aEmailSenderinstead of extending it.- Easier testing with fake sender injected.
- Swap implementations without subclass explosion.
Related: Object-Oriented Python Best Practices - design rules
9. Abstract Interface with abc
Enforce methods subclasses must implement.
from abc import ABC, abstractmethod
class Repository(ABC):
@abstractmethod
def get(self, item_id: int) -> dict: ...
class InMemoryRepo(Repository):
def get(self, item_id: int) -> dict:
return {"id": item_id}- Cannot instantiate ABC until abstract methods implemented.
- Documents extension points for plugins and tests.
Protocoloffers structural alternative - see type hints section.
Related: Abstract Base Classes - collections.abc
10. slots for Memory
Restrict attributes and reduce per-instance dict overhead.
class Coord:
__slots__ = ("x", "y")
def __init__(self, x: int, y: int) -> None:
self.x = x
self.y = y- Instances cannot gain arbitrary new attributes.
- Meaningful when creating millions of small objects.
- Incompatible with some multiple inheritance patterns - use judiciously.
Related: Immutability & Hashability - hashable points
Stack versions: This page was written for Python 3.14.0 (stable 3.14, maintenance 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, and uv 0.6+.