Intermediate20 min

Type hints and dataclasses

Describe the shape of your data so editors, reviewers, and mypy catch mistakes before runtime.

Why this matters in AI / ML / GenAI

Pydantic (FastAPI, LangChain) is built entirely on type hints. Typed configs and typed records stop the classic ML bug where a string "0.2" flows into a float parameter and silently changes behaviour three layers down.

Annotating functions

Hints are optional and not enforced at runtime — Python does not check them. Their value is tooling: autocomplete, editor warnings, and mypy in CI.

def chunk(text: str, size: int = 512) -> list[str]:

Modern syntax (Python 3.9+): list[str], dict[str, float], tuple[int, int]. For "may be None" use str | None (3.10+) instead of Optional[str].

Annotate the public boundary of your code — function signatures, config objects, API models. Do not annotate every local variable; that is noise.

Dataclasses

@dataclass generates __init__, __repr__, and __eq__ from annotated attributes. It turns a bag of dict keys into a real object with autocomplete.

frozen=True makes instances immutable — ideal for configs that should not mutate mid-run.

Mutable defaults need field(default_factory=list), the same trap as mutable function defaults.

Dict vs dataclass: use a dict for data crossing a JSON boundary; use a dataclass for structures your own code passes around. Typos become errors instead of silent None.

Validation with pydantic (production note)

Dataclasses do not validate. Pydantic does: it parses and coerces at construction and raises a clear error when a field is wrong.

FastAPI request bodies are pydantic models, so an invalid payload is rejected with a 422 before your code runs. Pydantic is not available in this browser sandbox, so the example below is copy-paste for your local environment.

Copy-paste examples

Copy into your own editor, or load one into the compiler below and press Run.

Typed functions

Hints do not run, but they document intent precisely.

def chunk_text(text: str, size: int = 40) -> list[str]:
    return [text[i:i + size] for i in range(0, len(text), size)]

def mean_score(scores: list[float]) -> float:
    return sum(scores) / len(scores) if scores else 0.0

def find_doc(doc_id: str, index: dict[str, str]) -> str | None:
    return index.get(doc_id)

print(chunk_text("Python type hints help teams read code faster.", 20))
print(mean_score([0.9, 0.8, 0.7]))
print(find_doc("missing", {"d1": "text"}))

A frozen config dataclass

frozen=True prevents accidental mutation of run configuration.

from dataclasses import dataclass, field

@dataclass(frozen=True)
class TrainConfig:
    model_name: str
    epochs: int = 3
    learning_rate: float = 2e-5
    tags: tuple[str, ...] = ()

cfg = TrainConfig(model_name="bert-base", tags=("nlp", "baseline"))
print(cfg)
print("lr:", cfg.learning_rate)

try:
    cfg.epochs = 10
except Exception as err:
    print("immutable:", type(err).__name__)

Dataclass for retrieved chunks

default_factory avoids the shared-mutable-default bug.

from dataclasses import dataclass, field

@dataclass
class Chunk:
    doc_id: str
    text: str
    score: float = 0.0
    tags: list[str] = field(default_factory=list)

hits = [
    Chunk("d1", "Python powers ML pipelines", 0.91, ["python"]),
    Chunk("d2", "Kubernetes runs containers", 0.55),
]
hits.sort(key=lambda c: c.score, reverse=True)
for h in hits:
    print(f"{h.doc_id}: {h.score:.2f} {h.tags} -> {h.text}")

Pydantic model (run locally)

Copy into your own project after pip install pydantic. This is what FastAPI validates with.

# pip install pydantic
from pydantic import BaseModel, Field

class ChatRequest(BaseModel):
    question: str = Field(min_length=1, max_length=2000)
    temperature: float = Field(default=0.2, ge=0.0, le=2.0)
    top_k: int = Field(default=4, ge=1, le=20)

req = ChatRequest(question="What is RAG?", temperature="0.7")  # coerced to float
print(req.temperature, type(req.temperature))
print(req.model_dump())

Model an eval record

Try it — in-browser Python

Add a latency_ms field with a default and print the average.

Output

Python runs in your browser. First run downloads the runtime.

Press Run (or Ctrl+Enter) to execute.

CPython in WebAssembly. Stdlib works. NumPy and pandas load on demand. No input(), no GPU, no network installs.

Takeaways

  • Type hints are documentation your editor and mypy can check; they do not run.
  • Dataclasses replace anonymous dicts for internal structures.
  • Use pydantic when data crosses a boundary and must be validated.