Módulo 3 - Dataclasses e estruturas de dados

frozen e __post_init__: imutabilidade e validação

10 min de leitura · por Cesar Gargiulo, revisado pela equipe ValorFinal e GuardiaSec · Atualizado em 01/07/2026

O que você vai aprender

  • Tornar uma dataclass imutável com frozen=True.
  • Entender o que a imutabilidade protege e o que ela impede.
  • Validar campos logo após a construção com __post_init__.
  • Garantir que um objeto nunca nasça em estado inválido.

frozen: objetos que não mudam

Passar frozen=True ao decorador torna as instâncias imutáveis. Depois de construído, o objeto não deixa você alterar nenhum campo: qualquer tentativa de atribuição levanta um erro. Isso pode parecer uma limitação, mas é uma garantia valiosa. Um objeto imutável não muda pelas suas costas, o que elimina toda uma classe de bugs de mutação acidental. E, como o conteúdo é estável, um objeto frozen pode ser usado como chave de dicionário ou item de conjunto, coisa que um objeto mutável não permite.

from dataclasses import dataclass

@dataclass(frozen=True)
class Coordenada:
    latitude: float
    longitude: float

c = Coordenada(-23.5, -46.6)
print(c)  # Coordenada(latitude=-23.5, longitude=-46.6)

# c.latitude = 0.0  # FrozenInstanceError: nao pode alterar

# Por ser imutavel, serve como chave de dicionario:
cidades = {c: "Sao Paulo"}
print(cidades[Coordenada(-23.5, -46.6)])  # Sao Paulo

frozen=True impede alterar campos e permite usar o objeto como chave de dict.

Dataclass normal

  • Campos podem ser alterados após a criação
  • Não serve como chave de dicionário
  • Mais flexível para objetos que evoluem
  • Risco de mutação acidental em código grande

Dataclass frozen

  • Campos ficam travados após a criação
  • Serve como chave de dict e item de set
  • Ideal para valores que representam um fato fixo
  • Sem mutação acidental: mais seguro em paralelo

__post_init__: validar na construção

O __init__ gerado pela dataclass apenas atribui os campos; ele não valida nada. Mas muitas vezes você quer garantir uma condição, por exemplo que um preço não seja negativo ou que uma data de início venha antes da de fim. Para isso existe o __post_init__, um método que a dataclass chama automaticamente logo depois de atribuir os campos. É o lugar certo para verificar os valores e levantar um erro se algo estiver errado, de modo que um objeto inválido nunca chegue a existir.

from dataclasses import dataclass

@dataclass
class Retangulo:
    largura: float
    altura: float

    def __post_init__(self):
        if self.largura <= 0 or self.altura <= 0:
            raise ValueError("Dimensoes devem ser positivas")

    def area(self) -> float:
        return self.largura * self.altura

r = Retangulo(3.0, 4.0)
print(r.area())  # 12.0

# Retangulo(-1.0, 4.0)  # ValueError: Dimensoes devem ser positivas

__post_init__ valida os campos logo após a construção; um objeto inválido nunca nasce.

Imutável e validado ao mesmo tempo

Os dois recursos combinam bem. Uma dataclass pode ser frozen e ter __post_init__: você valida na construção e, a partir daí, o objeto fica congelado e sempre consistente. Há um detalhe: como o frozen impede atribuições, se o __post_init__ precisar calcular e guardar um campo derivado, ele não pode fazê-lo com uma atribuição comum. Nesses casos raros, usa-se uma técnica específica que a documentação descreve. Para validação pura, que só verifica e levanta erro, frozen e __post_init__ convivem sem atrito.

from dataclasses import dataclass

@dataclass(frozen=True)
class Percentual:
    valor: float

    def __post_init__(self):
        if not 0.0 <= self.valor <= 100.0:
            raise ValueError("Percentual deve estar entre 0 e 100")

p = Percentual(42.5)
print(p.valor)  # 42.5

# Percentual(150.0)  # ValueError: fora da faixa
# p.valor = 10.0     # FrozenInstanceError: imutavel

frozen garante imutabilidade; __post_init__ garante que o valor nasça na faixa válida.

O resultado é um objeto que não pode nascer inválido nem mudar depois. Esse padrão, imutável mais validado, é comum em modelos de valor, como uma quantia em dinheiro, uma coordenada ou um percentual, onde a identidade é o próprio conteúdo e ele deve ser confiável. Você garante a correção uma vez, na construção, e nunca mais se preocupa com aquele objeto. A próxima aula compara a dataclass com outras estruturas de dados, para escolher a certa em cada situação.

Teste rápido

Qual é o papel do __post_init__ numa dataclass?

Perguntas frequentes

O que exatamente acontece se eu tentar alterar um campo frozen?
O Python levanta um FrozenInstanceError no momento da atribuição. O objeto permanece intacto e o programa é interrompido ali, deixando claro que aquela instância é imutável. É a garantia da imutabilidade: não há como mudar um campo de um objeto frozen depois de criado.
Por que um objeto imutável pode ser chave de dicionário e um mutável não?
Chaves de dicionário precisam ter um valor de hash estável. Um objeto mutável poderia mudar depois de virar chave, quebrando a busca. O frozen torna o objeto imutável e a dataclass gera um hash coerente com os campos, o que permite usá-lo com segurança como chave ou item de conjunto.
O __post_init__ recebe os campos como argumentos?
Não diretamente. Quando o __post_init__ roda, os campos já foram atribuídos pelo __init__, então você os acessa por self, como self.largura. Ele não recebe parâmetros nesse caso simples; apenas lê e valida o que já está no objeto. Há uma exceção para campos especiais que a documentação cobre.
Posso validar sem usar __post_init__?
Poderia validar depois, em cada lugar que usa o objeto, mas isso é frágil e repetitivo. Validar no __post_init__ concentra a regra num único ponto e garante que ela sempre roda na construção. Assim nenhum objeto inválido circula pelo programa, o que é bem mais robusto.
frozen protege também as listas dentro do objeto?
Só parcialmente. O frozen impede trocar o campo por outro objeto, mas se o campo for uma lista, você ainda pode alterar o conteúdo dela, porque a lista em si é mutável. Para imutabilidade real, prefira tuplas a listas nos campos de uma dataclass frozen.
Quando devo preferir uma dataclass frozen a uma normal?
Quando o objeto representa um valor que não deveria mudar depois de criado, como uma coordenada, uma quantia ou uma configuração fixa. O frozen evita mutações acidentais e permite usar o objeto como chave. Para entidades que evoluem ao longo do tempo, a dataclass normal costuma ser mais adequada.

Fontes

Seu progresso fica salvo neste aparelho. Assinantes sincronizam entre os aparelhos.