@dataclass gera métodos como __init__, __repr__ e __eq__ a partir dos campos anotados. Ela é uma boa escolha para objetos que agrupam dados com comportamento simples, sem transformar toda classe em um modelo automático.

Criar um modelo seguro

from dataclasses import dataclass, field


@dataclass(frozen=True, slots=True)
class Pedido:
    codigo: str
    itens: tuple[str, ...] = field(default_factory=tuple)

    def __post_init__(self) -> None:
        if not self.codigo.strip():
            raise ValueError("codigo vazio")


pedido = Pedido("A-42", ("livro", "curso"))
print(pedido)

O decorador lê os atributos anotados e cria o inicializador na ordem dos campos. Campos obrigatórios devem vir antes dos campos com padrão, inclusive em herança. Para configurações, kw_only=True obriga chamadas explícitas como Opcoes(host="db", timeout=2.0) e evita que uma mudança de ordem altere silenciosamente os argumentos.

Igualdade, ordenação e hash

Por padrão, duas instâncias da mesma classe são iguais quando todos os campos comparáveis têm valores iguais. Isso é igualdade de valor, não identidade. order=True também gera a ordenação e compara os campos como uma tupla na ordem declarada.

from dataclasses import dataclass, field


@dataclass(order=True)
class Tarefa:
    prioridade: int
    titulo: str = field(compare=False)


print(sorted([Tarefa(3, "publicar"), Tarefa(1, "revisar")]))

Excluir titulo é uma decisão de domínio: tarefas de mesma prioridade passam a ser iguais na comparação. Não use compare=False só para acomodar um teste. Defina quais campos realmente compõem o valor.

Uma dataclass congelada normalmente pode receber __hash__; uma mutável permanece não hashable para proteger conjuntos e chaves de dicionário contra mudanças posteriores. unsafe_hash=True existe para casos especiais, mas alterar um campo comparado depois da inserção pode quebrar buscas.

Validar e calcular campos

__post_init__ roda após o inicializador gerado e pode validar relações ou preencher um campo com init=False.

from dataclasses import dataclass, field
from decimal import Decimal


@dataclass(frozen=True)
class Item:
    quantidade: int
    preco: Decimal
    total: Decimal = field(init=False)

    def __post_init__(self) -> None:
        if self.quantidade <= 0 or self.preco < 0:
            raise ValueError("item invalido")
        object.__setattr__(
            self, "total", self.preco * self.quantidade
        )

object.__setattr__ é necessário porque a proteção congelada já está ativa. Use essa saída apenas para construir o valor derivado. Se a inicialização exigir rede, banco de dados ou muitos caminhos de recuperação, uma função fábrica nomeada comunica melhor o processo e mantém o construtor determinístico.

InitVar recebe um argumento no __init__ e o envia a __post_init__ sem armazená-lo. Serve para uma normalização curta; conversões complexas continuam mais claras em uma fábrica.

Copiar, converter e escolher o modelo

Ao revisar a classe, crie o menor caso válido, compare instâncias equivalentes e examine repr. Confirme que padrões mutáveis usam fábricas e que segredos estão excluídos da representação. Provoque cada invariante inválida e verifique se a mensagem orienta a correção. Se o objeto for chave de dicionário, garanta que o estado usado na comparação não muda.

Olhe também para a assinatura como usuário da API. Muitos opcionais podem indicar conceitos misturados; flags booleanas podem ficar mais claras como enum ou fábricas nomeadas. Teste chamadas posicionais e nomeadas, cópia com replace() e conversão de objetos aninhados. A dataclass reduz código cerimonial, mas não escolhe o contrato por você.

Em testes de serialização, compare explicitamente o resultado esperado e inclua datas, decimais e enums. Rode o verificador estático com uma chamada intencionalmente errada para confirmar que o arquivo faz parte da configuração. Meça o efeito de slots com volume realista e mantenha a versão simples quando não houver benefício observável.

dataclasses.replace(pedido, codigo="A-42-PROMO") cria uma nova instância e executa novamente a inicialização. É uma atualização conveniente para valores congelados. asdict() converte dataclasses aninhadas e copia profundamente outros valores, mas não é um serializador JSON completo. Datas, decimais, enums e objetos personalizados ainda precisam de representação explícita. Para um dicionário raso, selecione os campos ou percorra dataclasses.fields().

Dataclasses aceitam herança, mas composição costuma ser mais clara. Os métodos gerados precisam conciliar ordem, padrões e igualdade em toda a hierarquia. Um Endereco armazenado em Cliente tende a evoluir melhor que uma árvore de subclasses sem comportamento.

Use dataclass quando houver campos nomeados conhecidos, igualdade útil e poucas invariantes. Use NamedTuple quando o comportamento posicional de uma tupla imutável importar. Use TypedDict quando o valor em execução precisar continuar sendo dicionário. Prefira classe comum quando comportamento, encapsulamento ou um ciclo de construção personalizado forem predominantes.

Antes de ativar slots=True, meça a carga e confira a herança. A instância deixa de ter o __dict__ normal, salvo configuração específica, então atributos dinâmicos e algumas formas de reflexão mudam. weakref_slot=True habilita referências fracas quando necessárias. A economia pode ser relevante com muitos objetos pequenos, mas deve seguir uma medição.

frozen=True impede atribuições comuns depois da criação e permite representar um valor estável. Isso não torna recursivamente imutáveis os objetos guardados nos campos. slots=True pode reduzir memória e impedir atributos acidentais, mas deve ser adotado por necessidade, não como configuração automática.

Para campos mutáveis, nunca use itens: list[str] = []. Prefira field(default_factory=list), que cria uma lista por instância. Use __post_init__ para invariantes curtas; validações extensas e conversão de entrada externa podem ficar melhores em uma camada dedicada.

As anotações continuam sendo anotações. Combine dataclasses com type hints em Python e um verificador estático quando o contrato de tipos importar. Para objetos de domínio ricos, uma classe escrita explicitamente pode comunicar melhor suas regras.

A documentação oficial de dataclasses, consultada em 22 de julho de 2026, detalha field, frozen, slots, herança e o comportamento dos métodos gerados.