Dataclasses em Python: Guia Completo com Exemplos Práticos

Aprenda a usar dataclasses em Python para reduzir boilerplate. Guia completo com exemplos práticos de frozen, slots, herança, field() e comparação com Pydantic.

6 min de leitura Equipe python.dev.br

Se você já criou classes em Python apenas para armazenar dados, sabe o quanto de código repetitivo isso envolve: __init__, __repr__, __eq__… tudo escrito manualmente. As dataclasses, introduzidas no Python 3.7, resolvem exatamente esse problema, gerando automaticamente esses métodos a partir de simples anotações de tipo.

O Que São Dataclasses

Dataclasses são classes Python decoradas com @dataclass que geram automaticamente métodos especiais como __init__, __repr__, __eq__ e outros. A ideia é simples: você declara os atributos com type hints e o Python cuida do resto.

Veja a diferença entre uma classe tradicional e uma dataclass:

# Classe tradicional — muito boilerplate
class ProdutoTradicional:
    def __init__(self, nome: str, preco: float, estoque: int = 0):
        self.nome = nome
        self.preco = preco
        self.estoque = estoque

    def __repr__(self):
        return f"ProdutoTradicional(nome={self.nome!r}, preco={self.preco}, estoque={self.estoque})"

    def __eq__(self, other):
        if not isinstance(other, ProdutoTradicional):
            return NotImplemented
        return (self.nome, self.preco, self.estoque) == (other.nome, other.preco, other.estoque)
# Com dataclass — limpo e direto
from dataclasses import dataclass

@dataclass
class Produto:
    nome: str
    preco: float
    estoque: int = 0

As duas versões produzem o mesmo resultado, mas a dataclass tem três linhas em vez de treze. Essa redução de boilerplate é o principal motivo para adotar dataclasses no seu código Python.

Valores Padrão e a Função field()

Valores padrão simples funcionam como em qualquer classe. Porém, para tipos mutáveis (listas, dicionários, sets), você precisa usar field() com default_factory para evitar o clássico problema de objetos mutáveis compartilhados:

from dataclasses import dataclass, field

@dataclass
class Pedido:
    cliente: str
    itens: list[str] = field(default_factory=list)
    metadata: dict[str, str] = field(default_factory=dict)
    _total: float = field(init=False, default=0.0)  # não aparece no __init__

Os parâmetros mais úteis de field() são:

  • default_factory — função chamada para gerar o valor padrão
  • init=False — exclui o campo do __init__
  • repr=False — exclui o campo do __repr__
  • compare=False — exclui o campo das comparações (__eq__)

Validação com __post_init__

Dataclasses não fazem validação automática de dados. Para adicionar lógica de validação, use o método __post_init__, que é chamado logo após o __init__ gerado:

from dataclasses import dataclass

@dataclass
class ContaBancaria:
    titular: str
    saldo: float
    limite: float = 1000.0

    def __post_init__(self):
        if not self.titular.strip():
            raise ValueError("Titular não pode estar vazio")
        if self.saldo < -self.limite:
            raise ValueError(f"Saldo {self.saldo} excede o limite de {self.limite}")
        self.titular = self.titular.strip().title()

# Funciona
conta = ContaBancaria("  joão silva  ", 500.0)
print(conta.titular)  # "João Silva"

# Levanta ValueError
try:
    conta_invalida = ContaBancaria("", 100.0)
except ValueError as e:
    print(e)  # "Titular não pode estar vazio"

Se você precisa de validação robusta e automática baseada em tipos, o Pydantic é a ferramenta certa. A regra geral: use dataclasses para estruturas internas e Pydantic quando os dados vêm de fontes externas (APIs, formulários, arquivos).

Dataclasses Imutáveis com frozen

Ao passar frozen=True, a dataclass se torna imutável — qualquer tentativa de alterar um atributo levanta um FrozenInstanceError:

from dataclasses import dataclass

@dataclass(frozen=True)
class Coordenada:
    latitude: float
    longitude: float

ponto = Coordenada(-23.5505, -46.6333)
print(ponto)  # Coordenada(latitude=-23.5505, longitude=-46.6333)

# Isso levanta FrozenInstanceError
# ponto.latitude = 0.0

Dataclasses frozen também são automaticamente hasháveis, o que permite usá-las como chaves de dicionário ou em conjuntos. Isso é útil para modelar objetos de valor (value objects) no seu domínio.

Otimização de Memória com slots

A partir do Python 3.10, você pode usar slots=True para gerar __slots__ automaticamente, economizando memória e ganhando velocidade no acesso a atributos:

from dataclasses import dataclass

@dataclass(slots=True)
class Sensor:
    id: int
    tipo: str
    valor: float
    ativo: bool = True

Com slots=True, o Python não cria um __dict__ por instância. Em aplicações com milhares de objetos, como leitura de dados de sensores IoT ou processamento de dados com Pandas, essa economia de memória pode ser significativa.

Parâmetros Keyword-Only com kw_only

Também a partir do Python 3.10, kw_only=True força todos os campos a serem passados como argumentos nomeados:

from dataclasses import dataclass

@dataclass(kw_only=True)
class ConfiguracaoDB:
    host: str
    porta: int = 5432
    usuario: str
    senha: str
    banco: str

# Obrigatório usar nomes — mais legível e menos sujeito a erros
config = ConfiguracaoDB(
    host="localhost",
    usuario="admin",
    senha="secreta",
    banco="minha_app"
)

Esse recurso é excelente para classes de configuração onde a ordem dos parâmetros não é óbvia. Compare com a abordagem de gerenciar configurações com Pydantic Settings para projetos maiores.

Herança com Dataclasses

Dataclasses suportam herança normalmente. A classe filha herda todos os campos da classe mãe:

from dataclasses import dataclass, field
from datetime import datetime

@dataclass
class Evento:
    nome: str
    data: datetime

@dataclass
class EventoOnline(Evento):
    url: str
    plataforma: str = "Zoom"
    participantes: list[str] = field(default_factory=list)

evento = EventoOnline(
    nome="Python Brasil 2026",
    data=datetime(2026, 10, 15),
    url="https://pythonbrasil.org.br/live"
)
print(evento)

Uma regra importante: campos com valor padrão devem vir depois de campos sem valor padrão. Isso pode causar problemas em herança se a classe mãe tem campos com default e a filha adiciona campos obrigatórios. Use kw_only=True para contornar isso. Para entender melhor herança em Python, confira nosso guia de programação orientada a objetos.

Utilitários: asdict e astuple

As funções asdict() e astuple() convertem dataclasses para dicionários e tuplas, respectivamente. Muito útil para serialização:

from dataclasses import dataclass, asdict, astuple

@dataclass
class Usuario:
    nome: str
    email: str
    idade: int

user = Usuario("Maria", "maria@email.com", 28)

# Para dicionário — útil para JSON
dados = asdict(user)
print(dados)  # {'nome': 'Maria', 'email': 'maria@email.com', 'idade': 28}

# Para tupla — útil para banco de dados
registro = astuple(user)
print(registro)  # ('Maria', 'maria@email.com', 28)

Isso se integra bem com bibliotecas de banco de dados como SQLite e PostgreSQL, onde você pode converter dataclasses para inserções SQL facilmente.

Dataclass vs NamedTuple vs Pydantic

Quando usar cada uma?

CaracterísticadataclassNamedTuplePydantic
MutávelSim (padrão)NãoSim (padrão)
Validação automáticaNãoNãoSim
PerformanceRápidoMuito rápidoRápido (v2 com Rust)
Serialização JSONVia asdict()Via _asdict()Nativo
Uso idealDomínio internoDados imutáveis simplesDados externos/APIs

Use dataclasses para modelar objetos do domínio da sua aplicação. Use Pydantic quando precisar validar dados de fontes externas. Use NamedTuple para tuplas imutáveis e leves.

Exemplo Prático: Modelando um Sistema de Pedidos

Vamos juntar tudo em um exemplo mais completo:

from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum

class StatusPedido(Enum):
    PENDENTE = "pendente"
    PAGO = "pago"
    ENVIADO = "enviado"
    ENTREGUE = "entregue"

@dataclass
class ItemPedido:
    produto: str
    quantidade: int
    preco_unitario: float

    @property
    def subtotal(self) -> float:
        return self.quantidade * self.preco_unitario

@dataclass
class Pedido:
    cliente: str
    itens: list[ItemPedido] = field(default_factory=list)
    status: StatusPedido = StatusPedido.PENDENTE
    criado_em: datetime = field(default_factory=datetime.now)

    def __post_init__(self):
        if not self.cliente.strip():
            raise ValueError("Cliente é obrigatório")

    @property
    def total(self) -> float:
        return sum(item.subtotal for item in self.itens)

    def adicionar_item(self, produto: str, quantidade: int, preco: float):
        self.itens.append(ItemPedido(produto, quantidade, preco))

# Uso
pedido = Pedido(cliente="Ana Costa")
pedido.adicionar_item("Teclado Mecânico", 1, 350.00)
pedido.adicionar_item("Mouse Gamer", 2, 150.00)

print(f"Total: R$ {pedido.total:.2f}")  # Total: R$ 650.00
print(f"Status: {pedido.status.value}")  # Status: pendente

Esse padrão de modelagem com dataclasses é muito usado em projetos que seguem boas práticas Python e design patterns.

Considerações Finais

Dataclasses são uma das ferramentas mais úteis do Python moderno. Elas eliminam boilerplate sem sacrificar clareza, integram-se perfeitamente com type hints e oferecem opções avançadas como frozen, slots e kw_only.

Se você está começando, use dataclasses como substituto padrão para classes simples de dados. Para validação mais robusta, combine com tratamento de erros adequado ou migre para Pydantic. E para entender melhor como context managers podem ajudar a gerenciar recursos nos seus objetos, confira nosso artigo sobre context managers em Python.

Data classes em outras linguagens: Kotlin popularizou o conceito de data classes com data class, que gera automaticamente equals(), hashCode(), toString() e copy() — inspiração direta para as dataclasses do Python. Já Rust usa structs com #[derive(Debug, Clone, PartialEq)] para funcionalidade similar.

E

Equipe python.dev.br

Contribuidor do Python Brasil — Aprenda Python em Português