> ## Documentation Index
> Fetch the complete documentation index at: https://fastapi2day.codewithsiva.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Python Dataclasses: Clean Data Models Without Boilerplate

> Learn Python dataclasses — auto-generated constructors, field defaults, frozen instances, ordering, utility functions, and when to use them vs Pydantic.

Dataclasses were introduced in Python 3.7 (PEP 557) to solve a specific, common problem: when you create a class whose primary purpose is to hold data, you inevitably write the same boilerplate over and over — `__init__`, `__repr__`, `__eq__`. The `@dataclass` decorator eliminates this entirely. Python generates those methods automatically from your type-annotated fields, giving you a clean, readable data model with almost no ceremony. This page covers everything from basic usage to frozen instances, ordering, class variables, utility functions, and how dataclasses compare to Pydantic's `BaseModel`.

## What is a Dataclass?

A **dataclass** is a class decorated with `@dataclass` that automatically generates common dunder methods based on its type-annotated attributes. Import the decorator from the `dataclasses` module:

```python theme={null}
from dataclasses import dataclass
```

### Before and After

**Without dataclass — verbose boilerplate:**

```python theme={null}
class Student:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __repr__(self):
        return f"Student(name={self.name!r}, age={self.age!r})"

    def __eq__(self, other):
        return self.name == other.name and self.age == other.age
```

**With dataclass — concise and clear:**

```python theme={null}
from dataclasses import dataclass

@dataclass
class Student:
    name: str
    age: int

s = Student("John", 20)
print(s)   # Student(name='John', age=20)
```

Python automatically generates `__init__()`, `__repr__()`, and `__eq__()` from the type-annotated fields.

## Fields and Default Values

Every **type-annotated attribute** becomes a dataclass field:

```python theme={null}
@dataclass
class Product:
    id:    int
    name:  str
    price: float
```

Fields can have default values to make them optional:

```python theme={null}
@dataclass
class User:
    name:   str
    active: bool = True   # optional — defaults to True

u = User("Alice")
print(u)   # User(name='Alice', active=True)
```

<Warning>
  Never use a mutable object (like a list or dict) as a default value directly. All instances would **share the same object**, causing hard-to-debug bugs:

  ```python theme={null}
  # ❌ Wrong — all Team instances share the same list
  @dataclass
  class Team:
      members: list = []
  ```

  Use `field(default_factory=...)` instead:

  ```python theme={null}
  from dataclasses import dataclass, field

  # ✅ Correct — each instance gets its own fresh list
  @dataclass
  class Team:
      members: list = field(default_factory=list)
  ```
</Warning>

## Adding Methods

Dataclasses are still regular classes — you can add any methods you need:

```python theme={null}
@dataclass
class Rectangle:
    width:  int
    height: int

    def area(self) -> int:
        return self.width * self.height

    def perimeter(self) -> int:
        return 2 * (self.width + self.height)

r = Rectangle(10, 5)
print(r.area())      # 50
print(r.perimeter()) # 30
```

## Immutable Dataclasses with `frozen=True`

Pass `frozen=True` to make all instances immutable. Attempting to modify a field raises a `FrozenInstanceError`:

```python theme={null}
@dataclass(frozen=True)
class Point:
    x: int
    y: int

p = Point(3, 4)
# p.x = 10   # FrozenInstanceError: cannot assign to field 'x'
```

Frozen dataclasses are hashable by default, making them suitable as dictionary keys or set members.

## Ordering with `order=True`

Pass `order=True` to generate comparison methods (`<`, `<=`, `>`, `>=`) based on field values in declaration order:

```python theme={null}
@dataclass(order=True)
class Student:
    age:  int
    name: str

students = [Student(20, "Bob"), Student(18, "Alice"), Student(22, "Carol")]
print(sorted(students))
# [Student(age=18, name='Alice'), Student(age=20, name='Bob'), Student(age=22, name='Carol')]
```

## Decorator Options Summary

| Option         | Description                                              |
| -------------- | -------------------------------------------------------- |
| `frozen=True`  | Makes instances immutable (and hashable)                 |
| `order=True`   | Generates `<`, `<=`, `>`, `>=` comparison methods        |
| `slots=True`   | Uses `__slots__` for reduced memory usage (Python 3.10+) |
| `kw_only=True` | All fields must be passed as keyword arguments           |

```python theme={null}
@dataclass(frozen=True, order=True)
class Employee:
    id:   int
    name: str
```

## Instance Variables vs. Class Variables

### Instance Variables

Type-annotated attributes become instance fields — each object gets its own copy:

```python theme={null}
@dataclass
class Student:
    name: str
    age:  int

s1 = Student("Alice", 20)
s2 = Student("Bob", 22)
print(s1.name)   # Alice
print(s2.name)   # Bob
```

### Class Variables with `ClassVar`

Use `ClassVar` from `typing` to declare class-level attributes that are shared by all instances. Dataclasses exclude `ClassVar` fields from `__init__` and `__repr__`:

```python theme={null}
from dataclasses import dataclass
from typing import ClassVar

@dataclass
class Student:
    school: ClassVar[str] = "ABC School"   # class variable

    name: str
    age:  int

s = Student("John", 20)
print(Student.school)   # ABC School
print(s.school)         # ABC School
print(s)                # Student(name='John', age=20)  — school not shown
```

## Utility Functions

The `dataclasses` module provides three helpful utility functions:

### `asdict()` — Convert to Dictionary

```python theme={null}
from dataclasses import asdict

s = Student("John", 20)
print(asdict(s))   # {'name': 'John', 'age': 20}
```

### `astuple()` — Convert to Tuple

```python theme={null}
from dataclasses import astuple

print(astuple(s))  # ('John', 20)
```

### `replace()` — Create a Modified Copy

```python theme={null}
from dataclasses import replace

s2 = replace(s, age=21)
print(s2)   # Student(name='John', age=21)
print(s)    # Student(name='John', age=20)  — original unchanged
```

## Dataclass vs. Pydantic BaseModel

Both are used for structured data, but they serve different purposes:

| Feature                                         |      `@dataclass`      |      Pydantic `BaseModel`      |
| ----------------------------------------------- | :--------------------: | :----------------------------: |
| Auto-generates `__init__`, `__repr__`, `__eq__` |            ✅           |                ✅               |
| Runtime type validation                         |            ❌           |                ✅               |
| Automatic type coercion (`"25"` → `25`)         |            ❌           |                ✅               |
| Serialisation                                   |       `asdict()`       |         `model_dump()`         |
| Built-in JSON support                           |         Manual         |                ✅               |
| Detailed validation error messages              |            ❌           |                ✅               |
| Performance                                     |         Faster         | Slight overhead for validation |
| Best suited for                                 | Internal/domain models |  API request & response models |

### Type Validation Comparison

**Dataclass — no validation:**

```python theme={null}
@dataclass
class User:
    age: int

u = User("25")
print(u.age)         # "25"  — still a string!
print(type(u.age))   # <class 'str'>
```

**Pydantic — validates and coerces:**

```python theme={null}
from pydantic import BaseModel

class User(BaseModel):
    age: int

u = User(age="25")
print(u.age)         # 25  — coerced to int
print(type(u.age))   # <class 'int'>
```

## When to Use Each

**Use `@dataclass` when:**

* You're building internal domain or business models.
* You already trust the data (it was created by your own code).
* You want a lightweight container without validation overhead.
* Examples: `Product`, `Employee`, `Point`, `Config`

**Use Pydantic `BaseModel` when:**

* You're accepting external input (HTTP requests, JSON files, user forms).
* You need automatic type coercion and detailed error messages.
* You're building FastAPI request/response models.
* Examples: API request schemas, response bodies, configuration settings

<Tip>
  **Rule of thumb:** `@dataclass` → *"I already trust this data; I just need a convenient container."* Pydantic `BaseModel` → *"I don't trust this data yet; validate it before I use it."*
</Tip>
