Capítulo 36, Intermediário
dataclasses
Uma dataclass escreve por você o `__init__`, o `__repr__` e o `__eq__`. Para classes que existem principalmente para guardar dados, elimina um monte de código repetido.
Código deste capítulo: intermediario/cap36_dataclasses.py
O básico
Basta declarar os campos com tipo, e o decorador gera o resto:
from dataclasses import dataclass, field, asdict, replace
@dataclass
class Produto:
nome: str
preco: float
tags: list[str] = field(default_factory=list)
p = Produto("caneta", 3.5)
print(p)
print(p == Produto("caneta", 3.5))
Produto(nome='caneta', preco=3.5, tags=[])
True
O valor padrão mutável, outra vez
O mesmo problema do capítulo 18 volta aqui, e a dataclass o impede com um erro claro. Para campos mutáveis, use field(default_factory=...):
try:
@dataclass
class Ruim:
itens: list = []
except ValueError as erro:
print(erro)
mutable default <class 'list'> for field itens is not allowed: use default_factory
Imutável, ordenável e econômica
As opções do decorador cobrem os casos comuns. frozen=True impede a alteração depois de criado (e torna o objeto hashable). order=True gera os operadores de comparação. slots=True reduz a memória de cada instância:
@dataclass(frozen=True, order=True, slots=True)
class Versao:
major: int
minor: int = 0
v1, v2 = Versao(1, 2), Versao(1, 10)
print(v1 < v2, sorted([v2, v1]))
try:
v1.major = 5
except Exception as erro:
print(type(erro).__name__)
True [Versao(major=1, minor=2), Versao(major=1, minor=10)]
FrozenInstanceError
Validação, cópia e conversão
O método __post_init__ roda logo depois do __init__ e é o lugar da validação. As funções asdict e replace convertem para dicionário e criam uma cópia com campos trocados, sem mutar o original:
@dataclass
class Pedido:
cliente: str
itens: list[tuple[str, float]] = field(default_factory=list)
def __post_init__(self):
if not self.cliente:
raise ValueError("cliente é obrigatório")
@property
def total(self):
return sum(preco for _, preco in self.itens)
pedido = Pedido("Ana", [("caneta", 3.5), ("caderno", 18.9)])
print(round(pedido.total, 2))
print(asdict(pedido))
copia = replace(pedido, cliente="Bia")
print(copia.cliente, pedido.cliente)
22.4
{'cliente': 'Ana', 'itens': [('caneta', 3.5), ('caderno', 18.9)]}
Bia Ana
Quando usar cada opção
| Preciso de... | Uso |
|---|---|
| Um registro imutável e leve, que também se comporta como tupla | NamedTuple |
| Dados de fora (JSON) com forma conhecida, só para ferramentas de tipo | TypedDict |
| Um objeto de domínio com dados, métodos e validação simples | dataclass |
| Validação e conversão pesadas de dados de entrada | Uma biblioteca como o Pydantic |
Exercício 1
Um livro validado
Crie a dataclass Livro(titulo, paginas) que recuse paginas menor ou igual a zero com ValueError.