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.
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ãoinit=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ística | dataclass | NamedTuple | Pydantic |
|---|---|---|---|
| Mutável | Sim (padrão) | Não | Sim (padrão) |
| Validação automática | Não | Não | Sim |
| Performance | Rápido | Muito rápido | Rápido (v2 com Rust) |
| Serialização JSON | Via asdict() | Via _asdict() | Nativo |
| Uso ideal | Domínio interno | Dados imutáveis simples | Dados 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 automaticamenteequals(),hashCode(),toString()ecopy()— inspiração direta para as dataclasses do Python. Já Rust usa structs com#[derive(Debug, Clone, PartialEq)]para funcionalidade similar.
Equipe python.dev.br
Contribuidor do Python Brasil — Aprenda Python em Português