dataclasses vs Pydantic vs attrs: Which to Use

Python’s dataclasses, Pydantic, and attrs all cut class boilerplate, but only some validate data. Here’s which to reach for and why

Use a plain dataclass for trusted data inside your own code, Pydantic when data arrives from outside and must be validated, and attrs when you want dataclass-style classes with extra features like validators and converters. All three remove the boilerplate of writing __init__ and __repr__ by hand. What separates them is what they do with the types you write down.

The same class, three ways

from dataclasses import dataclass

@dataclass
class UserDC:
    name: str
    age: int

UserDC(name="Ada", age="36")   # accepted: the annotation isn't checked
from pydantic import BaseModel

class UserPD(BaseModel):
    name: str
    age: int

UserPD(name="Ada", age="36")   # "36" is converted to 36; bad input raises ValidationError
from attrs import define, field, validators

@define
class UserAttrs:
    name: str
    age: int = field(validator=validators.instance_of(int))

UserAttrs(name="Ada", age="36")   # raises TypeError

dataclasses: built in, no validation

@dataclass is in the standard library, so there is nothing to install. It builds __init__, __repr__ and comparison methods from your annotated fields, and its options cover the common needs: frozen=True for immutability, and slots=True and kw_only=True (both added in Python 3.10). If you want to check a value, you can do it yourself in __post_init__. The trade-off is that the type hints are documentation and tooling input, not runtime checks. That is exactly right for data your own code has already made sure about.

Pydantic: validation and parsing

Pydantic is for data you do not control. Its validation is driven by type annotations, it can convert compatible values ("36" becomes 36) or run in strict mode where nothing is converted, and its core validation logic is written in Rust. Models can also emit JSON Schema, which is how FastAPI generates its documentation. If the data comes from an HTTP request, a config file or a language model’s output, this is the tool. See what Pydantic is for more.

attrs: dataclasses with more features

attrs is the older third-party library that dataclasses are most often compared with (PEP 557 even has a section on why not just use attrs), and it goes further. Its modern attrs.define API turns on slots and annotation awareness by default, and it supports validators and converters, as the example above shows. Its own docs describe it as a kit for building the classes you would otherwise write by hand, with less boilerplate. Serialisation is handled by a separate sibling project, cattrs. It is a third-party dependency, but a small and focused one, which makes it a popular choice for libraries.

How they compare

dataclassesattrsPydantic
In the standard libraryYesNoNo
Checks types at runtimeNoOnly with validators you addYes, by default
Converts input valuesNoWith converters you addYes, with a strict mode available
Serialisationasdict and astupleSeparate cattrs projectBuilt in (model_dump)
Best forTrusted internal dataLibraries and richer plain classesData from outside your program

Which should you choose?

  • Passing structured data around inside your code: a dataclass. No dependency, and it is enough.
  • Anything that crosses a boundary (HTTP requests, files, environment variables, LLM output): Pydantic, so bad data fails at the edge with a clear error.
  • You need validators or converters on plain classes without Pydantic’s parsing, or you are writing a library: attrs.
  • Not sure: start with a dataclass and move to Pydantic the first time you find yourself writing validation code by hand.

These also combine well: it is common to validate at the edge with Pydantic and then work with simple dataclasses inside the application. For more on writing code that stays readable as it grows, see how to write clean Python code.

Frequently Asked Questions

What is the difference between dataclasses and Pydantic?

Dataclasses generate methods such as __init__ and __repr__ from your type annotations but do not enforce those annotations at runtime, so a field typed int will accept a string. Pydantic validates and converts the data against your types.

Is attrs the same as dataclasses?

They are related but not the same. PEP 557 notes that attrs supports extra features such as validators, converters and metadata, and that dataclasses leave them out in exchange for simplicity.

Should I use Pydantic or a dataclass for internal data?

A dataclass is usually enough for data that is already trusted and typed inside your own code. Reach for Pydantic when the data comes from outside, such as API requests, config files or LLM output, and needs checking.

Does Pydantic replace dataclasses?

No. They solve different problems. Dataclasses ship with Python, while Pydantic is a third-party package built for validating and parsing data.