Dataclasses
Introduced in Python 3.7, the `@dataclass` decorator automates the generation of boilerplates (such as `__init__()`, `__repr__()`, and comparisons) for classes ...
Definition
Introduced in Python 3.7, the @dataclass decorator automates the generation of boilerplates (such as __init__(), __repr__(), and comparisons) for classes primarily designed to hold structured data states.
Why It Exists
AI applications process rich structural responses (tokens, finish reasons, metadata, response models). Typing these as dicts leads to index key typos. Traditional class initializers require writing endless self.var = var lines. Dataclasses solve this, providing clean structured schemas with built-in validation capability.
How It Works
The decorator inspects class type annotations:
@dataclass DECORATOR
┌────────────────────────┐
│ Class variables + Type │
└───────────┬────────────┘
│
Translates into standard Python bytecode including:
- Dynamic __init__() with parameter assignments
- Beautiful __repr__() string output formatting
- Comparison support (__eq__, __lt__) out-of-the-box- Auto-Assignment: Attributes are converted into parameter arguments inside an auto-generated initialization schema.
- Immutability (Optional): Setting
frozen=Trueturns instances into immutable read-only records, which can also be hashed as dictionary keys.
Real-World Example
In FastAPI / Pydantic APIs, dataclasses represent user profile inputs, structured response payloads, or model hyperparameter configuration blocks.
Python Example
Here is a production dataclass representing an LLM response payload complete with post-initialization validation:
from dataclasses import dataclass, field
import time
@dataclass
class LLMResponse:
"""Structured data container representing model response metadata."""
prompt: str
response_text: str
model_name: str
input_tokens: int
output_tokens: int
execution_time: float = field(default=0.0)
total_tokens: int = field(init=False) # Computed post-initialization
def __post_init__(self):
# Enforce validation checks
if self.input_tokens < 0 or self.output_tokens < 0:
raise ValueError("Token counts cannot be negative.")
# Compute read-only/cached parameters
self.total_tokens = self.input_tokens + self.output_tokens
# Demo
if __name__ == "__main__":
resp = LLMResponse(
prompt="Tell me a joke",
response_text="Why do programmers wear glasses? Because they can't C#!",
model_name="gemini-1.5-flash",
input_tokens=4,
output_tokens=12,
execution_time=0.45
)
print(resp)
print("Total Tokens:", resp.total_tokens)Interview Questions
- Beginner: Why use dataclasses instead of standard dictionaries?
- Intermediate: What is the purpose of the
__post_init__method in a dataclass? - Advanced: How do you make a dataclass immutable (read-only) and hashable?
Interview Answers
- Beginner: Dataclasses enforce static type hints, prevent runtime typos (since attributes are accessed via dot notation
obj.nameinstead of string lookupsobj["name"]), and auto-generate readable debugging strings (__repr__). - Intermediate:
__post_init__runs automatically after the compiler-generated__init__is finished. It is used to perform parameter validation checks or calculate derived fields that depend on the initial inputs. - Advanced: Pass the parameter
frozen=Trueto the decorator:@dataclass(frozen=True). This blocks modification of attributes after creation, automatically generating a__hash__method that lets you use instances as dictionary keys or in sets.
Common Mistakes
- Defining mutable defaults: Declaring
tags: list = []. This raises aValueErrorbecause mutable defaults are shared across instances. Usefield(default_factory=list)instead.
Assignment
Write a dataclass called ChatMessage that takes role (str), content (str), and auto-assigns a timestamp float field using time.time inside __post_init__.