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.