Dataclasses
Esta aula apresenta o módulo `dataclasses` do Python, que simplifica a criação de classes que armazenam dados. Você aprenderá a usar o decorador `@dataclass`, definir campos com valores padrão, tornar objetos imutáveis com `frozen`, e comparar dataclasses com `namedtuple` e dicionários.
Dataclasses são um recurso introduzido no Python 3.7 (PEP 557) que fornece uma maneira concisa e legível de criar classes que são essencialmente contêineres de dados. Com o decorador @dataclass, você evita escrever métodos repetitivos como __init__, __repr__, __eq__ e outros, que são gerados automaticamente. Isso torna o código mais limpo e menos propenso a erros.
Nesta aula, exploraremos os principais recursos das dataclasses, desde a definição básica até opções avançadas como campos imutáveis e comparação com estruturas de dados tradicionais.
@dataclass
O decorador @dataclass é aplicado a uma classe e, com base nas anotações de tipo dos atributos, gera automaticamente métodos especiais. Para usar dataclasses, primeiro importe o módulo: from dataclasses import dataclass.
Exemplo básico:
from dataclasses import dataclass
@dataclass
class Pessoa:
nome: str
idade: int
email: str
# Uso
p = Pessoa(nome="Alice", idade=30, email="alice@example.com")
print(p) # Pessoa(nome='Alice', idade=30, email='alice@example.com')
print(repr(p)) # Pessoa(nome='Alice', idade=30, email='alice@example.com')
print(p == Pessoa("Alice", 30, "alice@example.com")) # TrueNote que não foi necessário definir __init__, __repr__ ou __eq__. A dataclass gera esses métodos automaticamente. A ordem dos campos na definição da classe determina a ordem dos argumentos no construtor.
Campos e defaults
Você pode atribuir valores padrão aos campos, tornando-os opcionais na criação. No entanto, campos com valor padrão devem vir após campos sem padrão (como em funções).
from dataclasses import dataclass
@dataclass
class Produto:
nome: str
preco: float = 0.0
quantidade: int = 0
p1 = Produto("Caneta") # preco=0.0, quantidade=0
p2 = Produto("Caderno", 15.50, 10)
print(p1) # Produto(nome='Caneta', preco=0.0, quantidade=0)
print(p2) # Produto(nome='Caderno', preco=15.5, quantidade=10)É possível usar tipos mutáveis como padrão, mas com cuidado. Para listas, dicionários ou outros objetos mutáveis, use field(default_factory=list) para evitar que o mesmo objeto seja compartilhado entre instâncias.
from dataclasses import dataclass, field
from typing import List
@dataclass
class Pedido:
itens: List[str] = field(default_factory=list)
p1 = Pedido()
p1.itens.append("item1")
p2 = Pedido()
print(p2.itens) # [] (não compartilha a lista)
O módulo field() também permite configurar outros parâmetros como init, repr, compare e hash.
frozen
Definir @dataclass(frozen=True) torna a classe imutável: após a criação, os atributos não podem ser alterados. Isso é útil para objetos que representam valores constantes ou para garantir que não haja modificações acidentais.
from dataclasses import dataclass
@dataclass(frozen=True)
class Ponto:
x: float
y: float
p = Ponto(1.0, 2.0)
# p.x = 3.0 # Isso gera um erro: dataclasses.FrozenInstanceError
Com classes congeladas, você pode usá-las como chaves em dicionários e em conjuntos, desde que todos os campos sejam hashable. O método __hash__ é gerado automaticamente se eq=True (padrão) e frozen=True. Se frozen=False, o hash não é gerado (a classe não é hashable).
vs namedtuple e dict
Antes das dataclasses, era comum usar namedtuple (do módulo collections) ou dicionários para armazenar dados. Vamos comparar:
- namedtuple: cria tuplas com nomes de campos. São imutáveis e compactas, mas não permitem valores padrão, métodos personalizados ou herança facilmente. Exemplo:
Ponto = namedtuple('Ponto', ['x', 'y']). - dict: flexível, mas sem validação de tipos, sem nomes de campos explícitos (acesso via strings) e sem métodos especiais como
__repr__automático. - dataclass: oferece o melhor dos dois mundos: imutabilidade opcional, valores padrão, métodos automáticos, suporte a herança e anotações de tipo. Além disso, é mais legível e fácil de manter.
Exemplo comparativo:
from collections import namedtuple
# namedtuple
PontoNT = namedtuple('PontoNT', ['x', 'y'])
p1 = PontoNT(1, 2)
print(p1.x, p1.y) # 1 2
# p1.x = 3 # Erro: imutável
# dicionário
d = {'x': 1, 'y': 2}
print(d['x'], d['y']) # 1 2
d['x'] = 3 # mutável
# dataclass
@dataclass(frozen=True)
class PontoDC:
x: float
y: float
p2 = PontoDC(1, 2)
print(p2.x, p2.y) # 1 2
# p2.x = 3 # Erro se frozen=True
Dataclasses são preferíveis quando você precisa de mais funcionalidades, como métodos personalizados, herança ou validação. Para casos simples e imutáveis, namedtuple pode ser suficiente. Dicionários são úteis para estruturas dinâmicas, mas perdem em segurança de tipos e clareza.
Boas práticas
- Sempre use anotações de tipo para todos os campos, mesmo que sejam tipos básicos.
- Evite valores padrão mutáveis sem
default_factory. - Prefira
frozen=Truepara objetos que não devem ser alterados. - Use
field(repr=False)para campos sensíveis (como senhas) que não devem aparecer no__repr__. - Para validação de dados, considere usar
__post_init__.
Referências
- Documentação oficial do módulo dataclasses
- PEP 557 – Data Classes
- Documentação de namedtuple
- Real Python: Data Classes in Python
- GeeksforGeeks: Data Classes in Python
- Mypy suporte a dataclasses
Exercícios
Crie uma dataclass chamada
Livrocom campos: titulo (str), autor (str), ano (int) e preco (float) com valor padrão 0.0.✓ Resposta:from dataclasses import dataclass @dataclass class Livro: titulo: str autor: str ano: int preco: float = 0.0Modifique a classe
Livropara que seja imutável (frozen). Teste a imutabilidade tentando alterar um atributo.✓ Resposta:from dataclasses import dataclass @dataclass(frozen=True) class Livro: titulo: str autor: str ano: int preco: float = 0.0 livro = Livro("1984", "George Orwell", 1949) # livro.titulo = "Novo titulo" # Isso gera FrozenInstanceErrorCrie uma dataclass
Alunocom campos: nome (str), notas (List[float]) com valor padrão uma lista vazia (use default_factory).✓ Resposta:from dataclasses import dataclass, field from typing import List @dataclass class Aluno: nome: str notas: List[float] = field(default_factory=list)Compare dataclass e namedtuple: crie uma namedtuple
Pontocom campos x e y, e uma dataclass equivalente. Mostre como criar uma instância de cada.✓ Resposta:from collections import namedtuple from dataclasses import dataclass # namedtuple PontoNT = namedtuple('PontoNT', ['x', 'y']) p_nt = PontoNT(1, 2) # dataclass @dataclass class PontoDC: x: float y: float p_dc = PontoDC(1, 2) print(p_nt, p_dc) # PontoNT(x=1, y=2) PontoDC(x=1, y=2)Use o método
__post_init__para validar que o campoanoemLivroseja maior que 1900. Se não for, levante um ValueError.✓ Resposta:from dataclasses import dataclass @dataclass class Livro: titulo: str autor: str ano: int preco: float = 0.0 def __post_init__(self): if self.ano <= 1900: raise ValueError("Ano deve ser maior que 1900") # Teste # livro = Livro("Título", "Autor", 1800) # ValueError