Dataclasses & Pydantic & TypedDict

Modern data structures: boilerplate killers and runtime validators

Managing classes primarily used to store data has historically involved a lot of boilerplate: writing __init__, __repr__, and __eq__ methods by hand. Dataclasses (Python 3.7+) and Pydantic solve this, but they serve different purposes. One optimizes for speed and syntax, the other for validation and correctness.

The Boilerplate Killer

The Old Way (Standard Class)
class Product:
    def __init__(self, name, price, stock=0):
        self.name = name
        self.price = price
        self.stock = stock

    def __repr__(self):
        return f"Product(name='{self.name}', ...)"

    def __eq__(self, other):
        if not isinstance(other, Product):
            return NotImplemented
        return (self.name == other.name and 
                self.price == other.price)
The New Way (Dataclass)
from dataclasses import dataclass

@dataclass
class Product:
    name: str
    price: float
    stock: int = 0

# FREE:
# - __init__
# - __repr__
# - __eq__
# - __hash__ (optional)

Native Dataclasses

Built into the standard library, dataclasses are lightweight code generators. They generate special methods based on type hints, but they DO NOT validate data at runtime.

Key Feature
Immutability with frozen=True
@dataclass(frozen=True)
class Config:
    host: str
    port: int

c = Config("localhost", 8080)
c.host = "127.0.0.1"  # Raises FrozenInstanceError!
# Also makes the object hashable (usable as dict keys)
Pros
  • Built-in (Standard Library)
  • Extremely lightweight (low memory overhead)
  • Great IDE support
  • Follows standard Python OOP semantics
Cons
  • No runtime validation ( types are documentation only )
  • JSON serialization requires custom logic (e.g. asdict)
  • Limited configuration options compared to Pydantic

Pydantic: Dataclasses on Steroids

Pydantic's BaseModel looks like a dataclass but behaves like a parser. It guarantees that the types of the output model match the types you defined, coercing data where possible.

Interactive Demo
from pydantic import BaseModel, ValidationError

class User(BaseModel):
    id: int
    name: str = "Anonymous"
    tags: list[str] = []

# Pydantic validates and converts types!
# User(id="123") -> id becomes integer 123

When to avoid dataclasses or Pydantic in favor of TypedDict

Sometimes you just want type hints for a regular dictionary without the overhead of a class or runtime validation. In these cases, use TypedDict.

Pattern: Pure Data Transfer

If you are receiving a massive JSON payload and just need to pass it to another function without inspecting or validating every field, creating thousands of Pydantic models might be too slow.

from typing import TypedDict

class UserDict(TypedDict):
    id: int
    name: str

# At runtime, this is just a plain dictionary! Zero overhead.
data: UserDict = {"id": 1, "name": "Alice"}

Head-to-Head Comparison

FeatureDataclassesPydantic BaseModelTypedDict
Primary GoalCode generation (Boilerplate reduction)Data Parsing & ValidationType Hinting for Dicts
Runtime ValidationNone (Types are hints only)Robust (Types are enforced)None (Standard Dict)
Type CoercionNo (int("5") fails)Yes ("5" → 5)No
PerformanceFast (Native Python class)Slower (Validation overhead)*Fastest (Zero overhead)
JSON SerializationManual (via dataclasses.asdict)Built-in (.model_dump_json())Native (It's just a dict!)
Best Use CaseInternal application state, configurationAPI payloads, External data inputsLarge JSON blobs, High performance

* Note: Pydantic V2 (written in Rust) is significantly faster than V1, narrowing the gap.

Best Practices

1. The Sandwich Pattern

Use Pydantic at the edges (API requests, database reads) to validate inputs. Use Dataclasses internally for business logic to keep it fast and dependency-free.

2. Freeze It

Prefer @dataclass(frozen=True) (or Pydantic's frozen=True). Immutability prevents a whole class of bugs and makes objects hashable.

3. Keep it Simple

Don't add methods to dataclasses if they don't operate on the data. Separating data containers from logic services is often cleaner (Anemic Domain Model).

Key Takeaways

  • Dataclasses are for efficient code generation (methods) inside standard classes.
  • Pydantic is for parsing and validating messy external data into structured models.
  • TypedDict is for typing plain dictionaries when performance is critical.
  • Validation is expensive; pay the cost at the system boundaries, not in every internal function loop.
What's Next?

You've learned modern Python data tools! Now let's explore data manipulation at scale with powerful libraries for processing large datasets.

  • Pandas - Industry-standard data manipulation and analysis
  • Dask & Vaex - Handle datasets larger than memory
  • Polars - Lightning-fast data processing with Rust