Trilha de aprendizado · Nível 11 · Tutorial 8

Modelar objetos de dados com dataclasses

Ao concluir, você poderá usar dataclasses para reduzir código repetitivo em objetos de dados, controlando inicialização, representação, comparação e comportamento de hash.

  • Nível: Intermediário
  • Duração: 25 min
  • 9 passos
Modelar objetos de dados com dataclasses

O que você vai percorrer

  1. Declarar um objeto de dados Crie uma dataclass simples para representar uma tarefa e reconheça os métodos gerados a partir dos campos declarados. 2 min
  2. Combinar campos obrigatórios e valores padrão Organize os campos de uma dataclass para criar instâncias com argumentos obrigatórios e padrões previsíveis. 2 min
  3. Criar coleções independentes com default_factory Crie listas de etiquetas separadas para cada instância e reconheça quando uma lista ainda é compartilhada por escolha explícita. 3 min
  4. Validar a construção com __post_init__ Normalize e valide uma Tarefa logo após a inicialização gerada pela dataclass. 3 min
  5. Escolher o que aparece e o que define igualdade Controle separadamente os campos exibidos por uma dataclass e os campos que definem a igualdade entre instâncias. 3 min
  6. Gerar uma ordenação coerente com o domínio Configure comparações de ordem geradas e use a sequência dos campos participantes para definir uma ordem natural de tarefas. 3 min
  7. Entender os limites de frozen Veja o que uma dataclass congelada bloqueia — e por que uma lista interna ainda pode mudar. 3 min
  8. Prever a disponibilidade de hash Use eq e frozen para decidir quando uma dataclass pode participar de conjuntos e ser chave de dicionários. 4 min
  9. Aplicar as decisões em um modelo completo Reúna as decisões de modelagem em uma classe mutável e em uma chave congelada, verificando os comportamentos no seu computador. 4 min

O que você vai aprender

  • Declarar campos e valores padrão em uma dataclass.
  • Criar valores mutáveis independentes com default_factory e validar a construção com __post_init__.
  • Selecionar opções de geração de representação, igualdade e ordenação conforme o domínio.
  • Prever os efeitos de frozen e eq sobre atribuições e disponibilidade de hash.

Antes de começar

  • Definir representações com __repr__ e __str__
  • Definir igualdade e ordenação entre objetos
  • Manter igualdade e hash coerentes
  • Documentar funções com docstrings e tipos simples
  • Decoradores e preservação de invariantes de objetos

Passo 1 de 9

Declarar um objeto de dados

Crie uma dataclass simples para representar uma tarefa e reconheça os métodos gerados a partir dos campos declarados.

Uma classe focada em dados

Menos código repetitivo

Quando uma classe serve principalmente para guardar dados, uma dataclass pode gerar automaticamente métodos comuns. Importe o decorador da biblioteca padrão e aplique-o à classe com @dataclass.

Primeira dataclass

Uma Tarefa tem dois campos: título e prioridade.

python
from dataclasses import dataclass

@dataclass
class Tarefa:
    titulo: str
    prioridade: int

Dos campos aos métodos

Os campos anotados orientam a criação dos métodos padrão.

Diagrama mostrando os campos titulo e prioridade de uma Tarefa apontando para os métodos gerados init, repr e eq.

Sem escrever esses métodos, a dataclass gera inicialização, representação e igualdade a partir dos campos.

Construa e observe

Campos viram argumentos

Nesta classe, titulo e prioridade viram argumentos do __init__ gerado. A representação também mostra os valores dos campos.

Use no seu Python

Execute este código no seu editor ou no interpretador Python.

python
tarefa = Tarefa("Enviar relatório", 2)

print(tarefa)
print(tarefa == Tarefa("Enviar relatório", 2))
print(tarefa == Tarefa("Enviar relatório", 3))

# Saída esperada:
# Tarefa(titulo='Enviar relatório', prioridade=2)
# True
# False

Dica

O que foi gerado

Sem uma implementação própria, a dataclass fornece __init__, __repr__ e __eq__. Assim, você constrói o objeto, obtém uma representação informativa e compara tarefas pelos dados declarados.

Anotação não valida o valor

Tipo declarado ≠ conversão automática

As anotações declaram os campos para a dataclass, mas não verificam nem convertem automaticamente os argumentos em tempo de execução. Portanto, este código é aceito na construção, embora o valor não corresponda ao tipo indicado:

Valor incompatível ainda é recebido

python
tarefa_estranha = Tarefa("Enviar relatório", "alta")
print(tarefa_estranha)
# Tarefa(titulo='Enviar relatório', prioridade='alta')

Atenção

Importante

prioridade: int documenta que a prioridade deve ser um inteiro; não impede, sozinha, que uma string seja passada. A validação durante a construção será tratada mais adiante.

Confira as relações

Associe a operação ao método

Relacione cada operação com o método que a dataclass gera para esta classe.

Toque em um item e depois no par correspondente.

Passo 2 de 9

Combinar campos obrigatórios e valores padrão

Organize os campos de uma dataclass para criar instâncias com argumentos obrigatórios e padrões previsíveis.

Obrigatórios primeiro, padrões depois

A ordem define a inicialização

Em uma dataclass, um campo sem valor padrão se torna um argumento obrigatório do __init__ gerado. Um campo com = recebe um padrão e pode ser omitido na construção.

Na declaração básica, coloque todos os campos obrigatórios antes dos campos com padrão. Essa ordem permite que o inicializador tenha uma assinatura válida e previsível.

Da declaração à assinatura

A ordem dos campos é preservada nos argumentos gerados.

Diagrama mostrando os campos titulo e prioridade como obrigatórios antes de concluida com valor padrão False, que formam a assinatura Tarefa(titulo, prioridade, concluida=False).

Campos declarados de cima para baixo: primeiro os obrigatórios; depois os que têm padrão.

Um padrão pode ser substituído

Uma Tarefa com status inicial

Crie este arquivo e execute-o no seu computador.

python
from dataclasses import dataclass

@dataclass
class Tarefa:
    titulo: str
    prioridade: int
    concluida: bool = False

primeira = Tarefa("Estudar dataclasses", 1)
segunda = Tarefa("Enviar relatório", 2, True)
terceira = Tarefa(titulo="Revisar código", prioridade=3, concluida=True)

print(primeira)
print(segunda)
print(terceira)

Exemplo

O que observar

primeira usa o padrão: concluida=False.

segunda substitui o padrão com o terceiro argumento posicional: concluida=True.

terceira faz a mesma substituição com argumentos nomeados. A ordem da classe corresponde à assinatura conceitual Tarefa(titulo, prioridade, concluida=False).

Dica

Padrão simples

Use valores simples, como False, 0 ou uma string, diretamente após =. Coleções mutáveis, como listas, exigem outro recurso, visto no próximo step.

Corrija a declaração

Ordene as linhas da classe

Reorganize as linhas para que esta dataclass gere um inicializador válido. Considere que titulo deve ser obrigatório e concluida deve começar como False.

  1. titulo: str
  2. concluida: bool = False
  3. class Tarefa:
  4. @dataclass

Passo 3 de 9

Criar coleções independentes com default_factory

Crie listas de etiquetas separadas para cada instância e reconheça quando uma lista ainda é compartilhada por escolha explícita.

O problema dos valores mutáveis

Uma lista nova para cada tarefa

Use field para configurar um campo de dataclass quando um valor padrão precisa ser criado para cada instância. Listas e dicionários são mutáveis: se fossem compartilhados como padrão, uma alteração em uma tarefa poderia aparecer em outra.

Por isso, dataclasses rejeitam uma lista ou um dicionário escrito diretamente como valor padrão de um campo. A solução é default_factory.

Coleções separadas

Cada construção sem argumento para etiquetas aciona uma nova criação de lista.

Diagrama com duas instâncias de Tarefa, cada uma apontando para uma lista de etiquetas diferente; a primeira lista contém urgente e a segunda está vazia.

Com default_factory=list, cada tarefa recebe sua própria lista de etiquetas.

Atenção

Não chame a fábrica

Escreva default_factory=list, e não default_factory=list(). A fábrica recebe um chamável sem argumentos; o __init__ gerado chamará list() quando for necessário criar o padrão.

Configurando o campo etiquetas

Dataclass com uma lista por instância

Digite e execute este exemplo no seu editor ou no interpretador Python.

python
from dataclasses import dataclass, field


@dataclass
class Tarefa:
    titulo: str
    concluida: bool = False
    etiquetas: list[str] = field(default_factory=list)


primeira = Tarefa("Enviar relatório")
segunda = Tarefa("Revisar orçamento")

primeira.etiquetas.append("urgente")

print(primeira.etiquetas)  # ['urgente']
print(segunda.etiquetas)   # []
print(primeira.etiquetas is segunda.etiquetas)  # False

O que acontece na construção

Ao omitir etiquetas, o inicializador gerado chama list() para aquela construção. Assim, primeira.etiquetas e segunda.etiquetas são objetos distintos. O campo continua aceitando uma lista fornecida explicitamente por quem cria a tarefa.

Prática: altere uma tarefa

Observe as referências independentes

No código anterior, adicione mais uma etiqueta a primeira.etiquetas e execute novamente. O que acontece com segunda.etiquetas? Explique o resultado usando default_factory.

Escreva pelo menos 80 caracteres (0/80).

Passar uma lista é compartilhar essa referência

Compartilhamento explícito

Agora execute este complemento após o código anterior.

python
etiquetas_da_equipe = ["financeiro"]

terceira = Tarefa("Conferir nota", etiquetas=etiquetas_da_equipe)
quarta = Tarefa("Arquivar comprovante", etiquetas=etiquetas_da_equipe)

terceira.etiquetas.append("mensal")

print(quarta.etiquetas)  # ['financeiro', 'mensal']
print(terceira.etiquetas is quarta.etiquetas)  # True

A fábrica não copia argumentos

default_factory só é usado quando o argumento etiquetas é omitido. Ao passar etiquetas_da_equipe para as duas tarefas, você fornece a mesma lista às duas instâncias. Não há cópia automática; por isso, alterar uma referência altera o conteúdo visto pela outra.

Preveja o resultado

Qual é o resultado de a.etiquetas is b.etiquetas neste código?

a = Tarefa("A")
b = Tarefa("B")

Passo 4 de 9

Validar a construção com __post_init__

Normalize e valide uma Tarefa logo após a inicialização gerada pela dataclass.

O ponto de validação após criar a instância

Depois do __init__ gerado

Em uma dataclass, o inicializador gerado recebe os argumentos e atribui os campos à instância. Se a classe define __post_init__, ele é chamado automaticamente em seguida.

Esse método é um bom lugar para ajustar valores e verificar regras do domínio que precisam ser verdadeiras no momento da construção.

Fluxo de construção

A normalização acontece antes das verificações que dependem do valor ajustado.

Diagrama em três etapas mostrando argumentos de uma Tarefa, atribuição aos campos da instância e execução de post init com normalização e validação.

Argumentos → campos atribuídos → __post_init__. Se uma regra falhar, a construção termina com uma exceção.

Normalizar e verificar regras

Uma Tarefa validada

strip() remove espaços no início e no fim antes de testar se restou conteúdo.

python
from dataclasses import dataclass, field


@dataclass
class Tarefa:
    titulo: str
    prioridade: int = 0
    etiquetas: list[str] = field(default_factory=list)

    def __post_init__(self):
        self.titulo = self.titulo.strip()

        if not self.titulo:
            raise ValueError("o título não pode ficar vazio")

        if self.prioridade < 0:
            raise ValueError("a prioridade não pode ser negativa")


tarefa = Tarefa("  Revisar relatório  ", prioridade=2)
print(tarefa.titulo)  # Revisar relatório

Dica

Ordem importa

Primeiro normalize o título; depois teste se ele ficou vazio. Assim, uma entrada como " " não passa pela validação só porque continha espaços.

Pratique a regra de prioridade

Complete a condição

No método abaixo, complete a expressão para rejeitar prioridades negativas:

if self.prioridade ___ 0:
    raise ValueError("a prioridade não pode ser negativa")

Construção não é alteração posterior

__post_init__ roda uma vez por construção

A chamada automática acontece quando Tarefa(...) cria a instância. Depois disso, uma atribuição comum a um campo não chama __post_init__ novamente.

Por exemplo, tarefa.prioridade = -1 altera o atributo sem disparar a validação acima. Portanto, __post_init__ protege a entrada na construção, não todas as mudanças futuras.

Preveja os resultados

Para cada situação, diga se uma instância é criada, qual título fica armazenado ou qual exceção ocorre. Inclua também o que acontece depois em uma alteração posterior:

Tarefa("  Planejar  ", 1)
Tarefa("   ", 1)
Tarefa("Ler", -1)

existente = Tarefa("Estudar", 0)
existente.prioridade = -1

Escreva pelo menos 80 caracteres (0/80).

Passo 5 de 9

Escolher o que aparece e o que define igualdade

Controle separadamente os campos exibidos por uma dataclass e os campos que definem a igualdade entre instâncias.

Dois papéis independentes para cada campo

Exibir não é comparar

Por padrão, a dataclass gera __repr__ e __eq__ usando seus campos. Com field, você pode decidir separadamente se um campo aparece na representação (repr) e se participa da igualdade (compare).

Isso é útil para etiquetas: elas podem ser metadados da tarefa, sem definir se duas tarefas representam o mesmo valor.

Campos em duas visões

A representação e a igualdade consultam configurações distintas.

Diagrama de uma Tarefa com os campos título, prioridade e etiquetas. A visão de representação mostra título e prioridade; a visão de igualdade compara título e prioridade, ignorando etiquetas.

repr controla a aparência em repr(objeto); compare controla os campos usados por ==.

Configurar etiquetas como metadados

Tarefa com etiquetas fora da exibição e da igualdade

Use repr=False e compare=False no mesmo campo quando ele não deve aparecer nem definir igualdade.

python
from dataclasses import dataclass, field

@dataclass
class Tarefa:
    titulo: str
    prioridade: int = 0
    etiquetas: list[str] = field(
        default_factory=list,
        repr=False,
        compare=False,
    )

primeira = Tarefa("Revisar relatório", 2, ["trabalho"])
segunda = Tarefa("Revisar relatório", 2, ["urgente"])

print(primeira)
# Tarefa(titulo='Revisar relatório', prioridade=2)

print(primeira == segunda)
# True

print(primeira.etiquetas)
# ['trabalho']

Dica

O campo continua acessível

repr=False não protege nem esconde o atributo: primeira.etiquetas continua funcionando normalmente. Ele só o remove da representação gerada.

Exemplo

Configurações independentes

Se usar apenas repr=False, as etiquetas somem de repr(tarefa), mas ainda participam de tarefa1 == tarefa2.

Se usar apenas compare=False, elas aparecem na representação, mas são ignoradas na igualdade.

Use ambas as opções somente quando quiser os dois efeitos.

Tipo concreto e opções do decorador

Quando a igualdade é gerada

Com o padrão eq=True, a igualdade gerada compara os campos cujo compare é verdadeiro, mas apenas entre instâncias do mesmo tipo concreto. Assim, uma instância de Tarefa não é igual a uma instância de outra classe, mesmo que os valores dos atributos pareçam iguais.

No decorador, repr=False impede a geração de __repr__, e eq=False impede a geração de __eq__. Essas opções não removem uma implementação que já exista na hierarquia da classe; apenas dizem à dataclass para não gerar aquele método.

Preveja o efeito

Considere etiquetas: list[str] = field(default_factory=list, repr=False). Duas Tarefa têm o mesmo título e prioridade, mas listas de etiquetas diferentes. Qual resultado é esperado para tarefa1 == tarefa2?

Escolha a configuração

Associe objetivo e configuração

Relacione cada objetivo à configuração adequada para o campo etiquetas.

Toque em um item e depois no par correspondente.

Passo 6 de 9

Gerar uma ordenação coerente com o domínio

Configure comparações de ordem geradas e use a sequência dos campos participantes para definir uma ordem natural de tarefas.

Quando uma dataclass pode ser ordenada

A configuração padrão não cria ordem

Uma dataclass comum gera igualdade, mas não cria automaticamente métodos como < e >. Portanto, comparar duas tarefas com < falha até que você peça essa geração.

Use order=True no decorador para gerar __lt__, __le__, __gt__ e __ge__. Essa opção depende de eq=True: não é válido solicitar ordenação com eq=False.

Uma ordem natural para Tarefa

Prioridade menor vem primeiro; em empate, o título decide.

python
from dataclasses import dataclass, field

@dataclass(order=True)
class Tarefa:
    prioridade: int
    titulo: str
    etiquetas: list[str] = field(default_factory=list, compare=False)

primeira = Tarefa(1, "Enviar proposta")
segunda = Tarefa(2, "Revisar orçamento")

print(primeira < segunda)  # True

Campos que participam da ordem

A ordem segue os campos declarados que participam da comparação.

Diagrama mostrando Tarefa comparada primeiro pela prioridade e, quando ela empata, pelo título; etiquetas ficam fora do fluxo de comparação.

Com order=True, a comparação é lexicográfica: prioridade, depois título. Como etiquetas usa compare=False, ela não participa.

A ordem é a ordem da declaração

Desempates automáticos

Os campos participantes são consultados na ordem em que aparecem na classe, como numa comparação de tuplas. Para Tarefa, o critério é equivalente a comparar (prioridade, titulo).

Assim, prioridade é o critério principal e título só resolve empates. As etiquetas não influenciam a ordem porque foram declaradas com compare=False.

Exemplo

Mesmo valor de prioridade

planejar = Tarefa(1, "Planejar reunião", ["equipe"])
apresentar = Tarefa(1, "Apresentar resultado", ["urgente"])

print(apresentar < planejar)  # True

As prioridades empatam. Então os títulos são comparados: “Apresentar resultado” vem antes de “Planejar reunião” em ordem lexicográfica.

Dica

Tipo concreto importa

As comparações de ordem geradas se aplicam a instâncias do mesmo tipo concreto. Não trate uma Tarefa como ordenável em relação a um objeto de outra classe, mesmo que ele tenha campos parecidos.

Pratique: ordene pelo critério configurado

Coloque em ordem crescente

Considere a classe Tarefa definida com @dataclass(order=True) e os campos prioridade, titulo e etiquetas, nesta ordem. etiquetas usa compare=False. Organize as instâncias da menor para a maior.

  1. Tarefa(1, "Atualizar site", ["web"])
  2. Tarefa(2, "Revisar contrato", ["jurídico"])
  3. Tarefa(1, "Enviar convite", ["evento"])
  4. Tarefa(3, "Arquivar notas", [])

Verifique a configuração

Qual declaração é inválida?

Qual opção tenta gerar uma ordenação de modo inválido?

Resumo

Critério de ordenação da Tarefa

Use uma ordem que expresse o domínio.

  • Sem order=True, a dataclass não gera comparações de ordem.
  • Com order=True, os campos participantes são comparados na ordem da declaração.
  • compare=False exclui um campo tanto da igualdade quanto da ordenação geradas.
  • Para Tarefa, prioridade define o critério principal e titulo resolve empates.
  • order=True exige eq=True.

Passo 7 de 9

Entender os limites de frozen

Veja o que uma dataclass congelada bloqueia — e por que uma lista interna ainda pode mudar.

O que frozen=True bloqueia

Campos não podem ser reatribuídos

Ao usar @dataclass(frozen=True), a instância é construída normalmente, mas seus campos não podem ser atribuídos nem removidos pelas operações usuais depois da construção. Isso é útil quando o objeto deve representar um valor estável.

A barreira está nos campos

A camada congelada protege a referência armazenada no campo; ela não congela automaticamente o objeto para o qual essa referência aponta.

Diagrama de uma dataclass congelada: campos título e etiquetas aparecem bloqueados para atribuição, enquanto os itens dentro da lista etiquetas aparecem com possibilidade de alteração.

frozen=True bloqueia trocar ou remover o campo; não torna profundamente imutável o valor guardado nele.

Uma tarefa congelada

Crie esta classe em um arquivo Python e execute os exemplos nas próximas telas.

python
from dataclasses import dataclass

@dataclass(frozen=True)
class TarefaCongelada:
    titulo: str
    etiquetas: list[str]


tarefa = TarefaCongelada("Estudar dataclasses", ["python"])
print(tarefa)

Trocar o campo falha

Atribuição e remoção são bloqueadas

Depois de criada, a tentativa de substituir titulo, substituir etiquetas ou remover um desses campos gera FrozenInstanceError. Essa exceção indica que a operação tentou alterar a estrutura da instância congelada.

Operações bloqueadas

Execute uma operação por vez para observar a exceção.

python
from dataclasses import FrozenInstanceError

try:
    tarefa.titulo = "Revisar dataclasses"
except FrozenInstanceError as erro:
    print(type(erro).__name__)

try:
    tarefa.etiquetas = ["revisão"]
except FrozenInstanceError as erro:
    print(type(erro).__name__)

try:
    del tarefa.titulo
except FrozenInstanceError as erro:
    print(type(erro).__name__)

A lista interna continua mutável

Modificar conteúdo não é trocar o campo

O campo etiquetas continua apontando para a mesma lista criada ou recebida na construção. Portanto, chamar um método mutável da própria lista, como append, altera seu conteúdo sem reatribuir o campo.

Alteração permitida

Este código não tenta substituir etiquetas; ele modifica a lista já armazenada.

python
tarefa.etiquetas.append("modelo")
print(tarefa.etiquetas)
# ['python', 'modelo']

Atenção

Frozen não é imutabilidade profunda

frozen=True não torna listas, dicionários ou outros objetos mutáveis internos imutáveis. Se o modelo precisa representar um valor estável, prefira campos com valores imutáveis — por exemplo, uma tupla de etiquetas — e evite expor objetos mutáveis que possam ser alterados.

Preveja o comportamento

Frozen e lista interna

Em uma instância de TarefaCongelada, tarefa.etiquetas.append("urgente") gera FrozenInstanceError.

Passo 8 de 9

Prever a disponibilidade de hash

Use eq e frozen para decidir quando uma dataclass pode participar de conjuntos e ser chave de dicionários.

As regras geradas por eq e frozen

Hash segue a igualdade e a estabilidade

Quando você não define __hash__, a dataclass decide seu comportamento a partir de eq e frozen:

  • eq=True e frozen=False (o padrão): define __hash__ como None. A instância não é hashável.
  • eq=True e frozen=True: gera __hash__ usando os campos que participam da comparação.
  • eq=False: não altera o hash herdado, com ou sem frozen=True. Em uma classe que deriva diretamente de object, o hash continua baseado na identidade.

A primeira regra evita usar como chave um objeto mutável cuja igualdade pode mudar depois de inserido em um dicionário ou conjunto.

Mapa de decisão do hash

Compare as três configurações mais comuns.

Diagrama mostrando uma dataclass mutável com igualdade sem hash, uma dataclass congelada com igualdade e hash por campos, e uma dataclass sem igualdade gerada com hash por identidade.

eq=True exige estabilidade para que a dataclass gere hash automaticamente; frozen=True fornece essa restrição sobre os campos.

Uma chave de tarefa hashável

Campos estáveis para uma chave

Uma chave congelada com str e int pode ser usada em um conjunto.

python
from dataclasses import dataclass

@dataclass(frozen=True)
class ChaveTarefa:
    prioridade: int
    titulo: str

chave = ChaveTarefa(1, "Enviar relatório")
pendentes = {chave}

print(hash(chave))
print(chave in pendentes)  # True

Como eq=True é padrão, ChaveTarefa compara prioridade e titulo. Como também é congelada, a dataclass gera um hash compatível com esses campos.

Um campo com compare=False fica fora da igualdade e, por padrão, também fica fora do hash gerado. Assim, metadados que não definem o valor da chave não precisam influenciar sua identificação.

Congelada não significa hashável em qualquer caso

Uma lista ainda impede o cálculo

Execute este exemplo no seu computador.

python
from dataclasses import dataclass

@dataclass(frozen=True)
class TarefaComEtiquetas:
    titulo: str
    etiquetas: list[str]

tarefa = TarefaComEtiquetas("Revisar proposta", ["trabalho"])
print(hash(tarefa))  # TypeError: unhashable type: 'list'

Atenção

Hash gerado não garante hash calculável

A classe acima recebe um __hash__ gerado porque usa eq=True e frozen=True. Porém, etiquetas participa da comparação e do hash por padrão, e uma list não é hashável. Por isso, hash(tarefa) lança TypeError.

Além disso, frozen=True bloqueia trocar o campo etiquetas, mas não impede operações como tarefa.etiquetas.append("urgente"). Não trate congelamento como imutabilidade profunda.

O que o congelamento bloqueia — e o que não bloqueia

A referência ao campo não pode ser substituída; o conteúdo da lista ainda pode mudar.

Comparação visual: tentativa bloqueada de substituir uma lista em uma dataclass congelada e operação permitida de adicionar item dentro da lista existente.

Para chaves e elementos de conjunto, escolha campos participantes hasháveis e com valores estáveis.

Decida pelo comportamento observável

Associe cada modelo ao resultado

Relacione cada configuração ao comportamento de hash mais provável.

Toque em um item e depois no par correspondente.

Resumo

Critério para usar como chave

Antes de colocar uma dataclass em um set ou como chave de dict, avalie igualdade, congelamento e os valores dos campos participantes.

  • Uma dataclass padrão é comparável por valor, mas não é hashável.
  • frozen=True com eq=True permite gerar hash, desde que os campos participantes sejam hasháveis.
  • compare=False exclui um campo da igualdade e, por padrão, do hash gerado.
  • Uma lista participante causa TypeError, mesmo em uma dataclass congelada.
  • Prefira uma chave separada, congelada e composta por valores hasháveis quando precisar de identificação estável.

Passo 9 de 9

Aplicar as decisões em um modelo completo

Reúna as decisões de modelagem em uma classe mutável e em uma chave congelada, verificando os comportamentos no seu computador.

Dois objetos, duas responsabilidades

Modelo final

Use Tarefa para o objeto que muda durante o trabalho: ela tem título, prioridade, estado de conclusão e etiquetas. Para agrupamento em conjuntos, use ChaveTarefa, menor e congelada.

Em Tarefa, apenas prioridade e titulo definem igualdade e ordenação. As etiquetas são metadados: não aparecem no repr nem participam das comparações.

Decisões por campo

Diagrama de uma Tarefa mutável com prioridade e título ligados a igualdade e ordenação; concluída apenas como estado exibido; etiquetas ligadas a uma fábrica de lista e marcadas como fora de representação e comparação. Ao lado, uma ChaveTarefa congelada com prioridade e título ligada a um conjunto.

A classe mutável concentra o estado de trabalho; a chave congelada reúne somente valores estáveis e hasháveis.

Monte e execute o modelo

Prática local

Crie um arquivo chamado modelo_tarefa.py, copie o código completo abaixo e execute python modelo_tarefa.py. Se todos os assert passarem, o script termina sem mostrar erro.

modelo_tarefa.py

python
from dataclasses import dataclass, field


@dataclass(order=True)
class Tarefa:
    prioridade: int
    titulo: str
    concluida: bool = field(default=False, compare=False)
    etiquetas: list[str] = field(
        default_factory=list,
        repr=False,
        compare=False,
    )

    def __post_init__(self) -> None:
        self.titulo = self.titulo.strip()
        if not self.titulo:
            raise ValueError("O título não pode ficar vazio.")
        if self.prioridade < 0:
            raise ValueError("A prioridade não pode ser negativa.")


@dataclass(frozen=True)
class ChaveTarefa:
    prioridade: int
    titulo: str


# Padrão simples, normalização e representação.
primeira = Tarefa(2, "  Estudar dataclasses  ")
assert primeira.titulo == "Estudar dataclasses"
assert primeira.concluida is False
assert repr(primeira) == (
    "Tarefa(prioridade=2, titulo='Estudar dataclasses', concluida=False)"
)

# Cada chamada sem etiquetas cria uma lista independente.
segunda = Tarefa(1, "Enviar relatório")
primeira.etiquetas.append("python")
assert primeira.etiquetas == ["python"]
assert segunda.etiquetas == []

# Prioridade e título definem igualdade e os desempates da ordem.
mesmos_dados = Tarefa(2, "Estudar dataclasses", etiquetas=["urgente"])
assert primeira == mesmos_dados
ordenadas = sorted([
    Tarefa(2, "Zerar caixa de entrada"),
    Tarefa(1, "Enviar relatório"),
    Tarefa(2, "Aprender testes"),
])
assert [t.titulo for t in ordenadas] == [
    "Enviar relatório",
    "Aprender testes",
    "Zerar caixa de entrada",
]

# A validação ocorre na construção.
try:
    Tarefa(-1, "Inválida")
except ValueError as erro:
    assert str(erro) == "A prioridade não pode ser negativa."
else:
    raise AssertionError("Era esperado ValueError")

# Tarefa é mutável e, com eq=True, não é hashável.
primeira.concluida = True
try:
    hash(primeira)
except TypeError:
    pass
else:
    raise AssertionError("Tarefa não deveria ser hashável")

# A chave congelada, feita apenas de valores hasháveis, pode ir para um conjunto.
chaves = {
    ChaveTarefa(1, "Enviar relatório"),
    ChaveTarefa(1, "Enviar relatório"),
    ChaveTarefa(2, "Estudar dataclasses"),
}
assert len(chaves) == 2

print("Verificações concluídas.")

Confira e explique suas decisões

Relato da execução

Depois de executar o script, relate: qual comportamento comprovou que as etiquetas são independentes; quais campos decidiram a igualdade e a ordem; e por que Tarefa falha em hash(), mas ChaveTarefa entra no conjunto.

Escreva pelo menos 180 caracteres (0/180).

Síntese de modelagem

Resumo

Checklist para dataclasses

  • Declare campos obrigatórios antes dos campos com padrão e use o padrão simples quando ele puder ser compartilhado com segurança.
  • Para listas, dicionários e outros valores mutáveis criados por instância, use field(default_factory=...).
  • Use __post_init__ para normalizar e validar na construção; alterações posteriores exigem outra estratégia de controle.
  • Escolha deliberadamente os campos de repr, igualdade e ordenação: essas decisões são independentes.
  • Uma dataclass mutável com igualdade gerada normalmente não é hashável. Para uma chave de conjunto ou dicionário, prefira um modelo congelado cujos campos participantes sejam estáveis e hasháveis.

Tutorial concluído

Parabéns! Você concluiu: Modelar objetos de dados com dataclasses

Você consolidou o uso de dataclasses para modelar dados com inicialização, validação, comparação, ordenação, mutabilidade e hash coerentes.

Baixe o Aplicativo agora para ter acesso a + de 5000 cursos gratuitos, exercícios, certificado e muito conteúdo sem pagar nada!

  • Cursos online 100% gratuitos do início ao fim

    Milhares de cursos online em vídeo, ebooks e áudiobooks.

  • Mais de 60 mil exercícios gratuitos

    Para testar seus conhecimentos no decorrer dos cursos online

  • Certificado Digital gratuito válido em todo o Brasil

    Gerado diretamente na galeria de fotos do seu celular e enviado ao seu e-mail

Aplicativo Cursa na tela de ebook, na tela de curso em vídeo e na tela de exercícios do curso, mais o certificado de conclusão de curso