Python Dataclasses

Muitas classes servem só para guardar dados: um produto tem nome, preço e estoque; um aluno tem nome e nota. Para cada uma delas você escreve o mesmo __init__ com self.nome = nome, o mesmo __repr__, o mesmo __eq__ (aula Métodos Especiais). É trabalho repetido, e é fácil esquecer um campo em algum lugar. A dataclass ("classe de dados") resolve isso: você lista os campos, e o Python escreve esses métodos sozinho.

Você vai aprender
  • A criar uma classe de dados com @dataclass, a partir do módulo dataclasses.
  • O que o Python escreve por você: __init__, __repr__ e __eq__.
  • Campos com valor padrão, e a regra da ordem dos campos.
  • Por que itens: list = [] dá ValueError, e como o field(default_factory=list) resolve.
  • frozen=True: objetos que não podem mudar (e o FrozenInstanceError).
  • order=True: objetos que sabem se ordenar, e por que a ordem dos campos importa.
  • __post_init__, para conferir os dados quando o objeto nasce.

A ideia: a ficha de cadastro

Quando a lojinha da Ana cadastra um produto, ela preenche uma ficha impressa com três campos: nome, preço, estoque. Ninguém redesenha a ficha a cada produto; a gráfica imprimiu o modelo uma vez e pronto. A dataclass é essa ficha: você diz quais campos existem, e o Python "imprime" o resto (o jeito de preencher, o jeito de mostrar, o jeito de comparar duas fichas).


1. O jeito trabalhoso (o que você já sabe fazer)

class Produto:
    def __init__(self, nome, preco, estoque=0):
        self.nome = nome
        self.preco = preco
        self.estoque = estoque

    def __repr__(self):
        return f"Produto(nome={self.nome!r}, preco={self.preco!r}, estoque={self.estoque!r})"

    def __eq__(self, outro):
        if not isinstance(outro, Produto):
            return NotImplemented
        return (self.nome, self.preco, self.estoque) == (outro.nome, outro.preco, outro.estoque)


cafe = Produto("Café", 18.9, 12)
print(cafe)
print(cafe == Produto("Café", 18.9, 12))
$ python3 sem_dataclass.py
Produto(nome='Café', preco=18.9, estoque=12)
True

Funciona, mas repare: cada campo aparece cinco vezes (no parâmetro, no self.… = …, no __repr__ e duas no __eq__). Se amanhã a Ana acrescentar o campo validade, ela tem de lembrar de mexer em quatro lugares.


2. A mesma classe com @dataclass

from dataclasses import dataclass


@dataclass
class Produto:
    nome: str
    preco: float
    estoque: int = 0


cafe = Produto("Café", 18.9, 12)
leite = Produto("Leite", 5.49)
print(cafe)
print(leite)
print(cafe.nome, cafe.preco)
print(cafe == Produto("Café", 18.9, 12))
print(Produto(nome="Pão", preco=0.75, estoque=40))
cafe.estoque -= 1
print(cafe)
from dataclasses import dataclass
Traz o decorador dataclass do módulo dataclasses, que já vem com o Python (aula Módulos e import).
@dataclass
O decorador, logo acima do class. Ele lê os campos e escreve os métodos da classe (decoradores têm aula própria: Decoradores).
nome: str
Um campo: o nome, dois-pontos e o tipo (a mesma escrita da aula Dicas de Tipo). É o tipo que transforma a linha num campo.
preco: float
Outro campo, obrigatório (não tem valor padrão).
estoque: int = 0
Campo com valor padrão: se ninguém disser o estoque, ele vale 0.
$ python3 com_dataclass.py
Produto(nome='Café', preco=18.9, estoque=12)
Produto(nome='Leite', preco=5.49, estoque=0)
Café 18.9
True
Produto(nome='Pão', preco=0.75, estoque=40)
Produto(nome='Café', preco=18.9, estoque=11)

Cinco linhas de classe, e veio tudo: o objeto se criou com os três valores (ou com dois, usando o padrão do estoque), se mostrou de um jeito legível, foi comparado campo a campo com o == e aceitou argumentos nomeados. E continua sendo uma classe normal: dá para ler e mudar os atributos (cafe.estoque -= 1) e escrever os seus próprios métodos.

O que o @dataclass escreveu

MétodoO que fazEquivale a
__init__Recebe os campos, na ordem em que foram escritosdef __init__(self, nome, preco, estoque=0): self.nome = nome …
__repr__Mostra a classe e cada campo com o nomeProduto(nome='Café', preco=18.9, estoque=12)
__eq__Dois objetos são iguais se todos os campos forem iguais(self.nome, self.preco, …) == (outro.nome, outro.preco, …)
O tipo é só uma etiqueta

O str e o float dos campos não são conferidos quando o objeto nasce. Eles documentam o que se espera, e ferramentas como o editor de código usam essa informação, mas o Python aceita qualquer coisa:

from dataclasses import dataclass


@dataclass
class Produto:
    nome: str
    preco: float


estranho = Produto(42, "barato")
print(estranho)
$ python3 tipos.py
Produto(nome=42, preco='barato')

Para conferir os valores de verdade, use o __post_init__ (seção 6).

Missão: rode e veja o <__main__.Aluno object at …>. Depois, transforme a classe numa dataclass com os campos nome: str e nota: float (o __init__ escrito à mão pode sumir).

class Aluno:
    def __init__(self, nome, nota):
        self.nome = nome
        self.nota = nota


bia = Aluno("Bia", 8.5)
print(bia)
from dataclasses import dataclass


@dataclass
class Aluno:
    nome: str
    nota: float


bia = Aluno("Bia", 8.5)
print(bia)

3. Valores padrão: os campos com padrão vêm por último

Como numa função (aula Valores Padrão), os campos com valor padrão têm de vir depois dos campos sem. Se a Ana puser o estoque no meio:

from dataclasses import dataclass


@dataclass
class Produto:
    nome: str
    estoque: int = 0
    preco: float
$ python3 ordem_padrao.py
Traceback (most recent call last):
  File "/home/ana/ordem_padrao.py", line 4, in <module>
    @dataclass
     ^^^^^^^^^
  File "/usr/lib/python3.14/dataclasses.py", line 1442, in dataclass
    return wrap(cls)
  File "/usr/lib/python3.14/dataclasses.py", line 1432, in wrap
    return _process_class(cls, init, repr, eq, order, unsafe_hash,
                          frozen, match_args, kw_only, slots,
                          weakref_slot)
  File "/usr/lib/python3.14/dataclasses.py", line 1136, in _process_class
    _init_fn(all_init_fields,
    ~~~~~~~~^^^^^^^^^^^^^^^^^
             std_init_fields,
             ^^^^^^^^^^^^^^^^
    ...<9 lines>...
             slots,
             ^^^^^^
             )
             ^
  File "/usr/lib/python3.14/dataclasses.py", line 687, in _init_fn
    raise TypeError(f'non-default argument {f.name!r} '
                    f'follows default argument {seen_default.name!r}')
TypeError: non-default argument 'preco' follows default argument 'estoque'

Um traceback comprido assusta, mas o jeito de ler é sempre o mesmo:

  • A primeira parte é o seu arquivo: File "/home/ana/ordem_padrao.py", line 4, o @dataclass. Foi ali que o problema começou.
  • O meio (/usr/lib/python3.14/dataclasses.py) é o código do próprio Python, por onde o erro passou. Você não precisa entender essas linhas; elas não são o defeito. (No editor do guia, o traceback sai igual, com o mesmo caminho e os mesmos números de linha.)
  • A última linha é a explicação: TypeError: non-default argument 'preco' follows default argument 'estoque', "o argumento sem padrão preco vem depois do argumento com padrão estoque".

O erro aparece no @dataclass, antes mesmo de criar qualquer produto: ao escrever o __init__, o Python teria de fazer def __init__(self, nome, estoque=0, preco), o que não é permitido. Como consertar: passe os campos com padrão para o fim (nome, preco, estoque = 0).


4. A armadilha da lista: field(default_factory=list)

A Ana quer um carrinho que já nasça com uma lista vazia de itens. O natural seria escrever = []:

from dataclasses import dataclass


@dataclass
class Carrinho:
    dona: str
    itens: list = []
$ python3 lista_padrao.py
Traceback (most recent call last):
  File "/home/ana/lista_padrao.py", line 4, in <module>
    @dataclass
     ^^^^^^^^^
  File "/usr/lib/python3.14/dataclasses.py", line 1442, in dataclass
    return wrap(cls)
  File "/usr/lib/python3.14/dataclasses.py", line 1432, in wrap
    return _process_class(cls, init, repr, eq, order, unsafe_hash,
                          frozen, match_args, kw_only, slots,
                          weakref_slot)
  File "/usr/lib/python3.14/dataclasses.py", line 1066, in _process_class
    cls_fields.append(_get_field(cls, name, type, kw_only))
                      ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.14/dataclasses.py", line 917, in _get_field
    raise ValueError(f'mutable default {type(f.default)} for field '
                     f'{f.name} is not allowed: use default_factory')
ValueError: mutable default <class 'list'> for field itens is not allowed: use default_factory

De novo: o começo aponta o seu @dataclass, o meio é o código do Python, e a última linha explica: "valor padrão mutável (do tipo list) para o campo itens não é permitido: use default_factory". O próprio erro diz o conserto.

Por que o Python proíbe?

Uma lista pode mudar (é mutável; a aula Mutável × Imutável explica a fundo). Um valor padrão é criado uma vez só, quando a classe é escrita. Se fosse permitido, todos os carrinhos sem lista própria iam dividir a mesma lista. É exatamente o que acontece numa classe comum, que não tem essa proteção:

class Carrinho:
    def __init__(self, dona, itens=[]):
        self.dona = dona
        self.itens = itens


ana = Carrinho("Ana")
bia = Carrinho("Bia")
ana.itens.append("café")
print(bia.itens)
$ python3 mesma_lista.py
['café']

A Ana pôs café no carrinho dela, e o café apareceu no da Bia, sem erro nenhum. A dataclass prefere dar o ValueError logo de cara a deixar esse defeito passar calado.

O conserto: uma "fábrica" de valores

from dataclasses import dataclass, field


@dataclass
class Carrinho:
    dona: str
    itens: list = field(default_factory=list)


ana = Carrinho("Ana")
bia = Carrinho("Bia")
ana.itens.append("café")
print(ana)
print(bia)
print(ana.itens is bia.itens)
from dataclasses import dataclass, field
Agora também a função field ("campo"), que configura um campo com mais detalhes.
field(default_factory=list)
Default factory quer dizer "fábrica do valor padrão". Você passa list sem os parênteses: não é uma lista, é a "receita" que cria uma lista. A cada carrinho novo, o Python chama list() e entrega uma lista vazia nova.
$ python3 factory.py
Carrinho(dona='Ana', itens=['café'])
Carrinho(dona='Bia', itens=[])
False

O café ficou só no carrinho da Ana, e ana.itens is bia.itens deu False: são duas listas diferentes. Vale o mesmo para dicionários (field(default_factory=dict)) e conjuntos (field(default_factory=set)).

Missão: o programa dá ValueError. Conserte o campo itens com field(default_factory=list), sem esquecer de importar o field.

from dataclasses import dataclass


@dataclass
class Carrinho:
    dona: str
    itens: list = []


ana = Carrinho("Ana")
bia = Carrinho("Bia")
ana.itens.append("café")
print(ana)
print(bia)
from dataclasses import dataclass, field


@dataclass
class Carrinho:
    dona: str
    itens: list = field(default_factory=list)


ana = Carrinho("Ana")
bia = Carrinho("Bia")
ana.itens.append("café")
print(ana)
print(bia)

5. frozen=True: a ficha plastificada

Algumas fichas não devem mudar depois de prontas. Com @dataclass(frozen=True) ("congelada"), o objeto fica imutável: qualquer tentativa de mudar um campo dá erro.

from dataclasses import dataclass


@dataclass(frozen=True)
class Produto:
    nome: str
    preco: float


cafe = Produto("Café", 18.9)
print(cafe)
cafe.preco = 21.0
$ python3 frozen.py
Produto(nome='Café', preco=18.9)
Traceback (most recent call last):
  File "/home/ana/frozen.py", line 12, in <module>
    cafe.preco = 21.0
    ^^^^^^^^^^
  File "<string>", line 16, in __setattr__
dataclasses.FrozenInstanceError: cannot assign to field 'preco'
@dataclass(frozen=True)
Com parênteses, o decorador aceita opções. frozen=True liga o congelamento.
File "/home/ana/frozen.py", line 12
O erro foi na linha da mudança, cafe.preco = 21.0.
File "<string>", line 16, in __setattr__
<string> quer dizer "um código que não está em arquivo nenhum": é o __setattr__ (o método especial chamado em toda atribuição objeto.campo = valor) que o @dataclass escreveu para bloquear a mudança.
dataclasses.FrozenInstanceError: cannot assign to field 'preco'
"Erro de objeto congelado: não é possível atribuir ao campo preco." O nome vem com dataclasses. na frente porque é uma exceção desse módulo.

Mudou o preço? Faça uma ficha nova

Com um objeto congelado, você não altera: cria outro, com a função replace ("substituir"). E, de brinde, o objeto congelado pode entrar num conjunto ou virar chave de dicionário (o que a dataclass comum não pode, pelo mesmo motivo do __hash__ da aula Métodos Especiais):

from dataclasses import dataclass, replace


@dataclass(frozen=True)
class Produto:
    nome: str
    preco: float


cafe = Produto("Café", 18.9)
cafe_novo = replace(cafe, preco=21.0)
print(cafe)
print(cafe_novo)
vistos = {cafe, Produto("Café", 18.9), cafe_novo}
print(len(vistos))
$ python3 frozen_replace.py
Produto(nome='Café', preco=18.9)
Produto(nome='Café', preco=21.0)
2

O café original continua a 18,90; o novo custa 21,00. No conjunto, os dois cafés de 18,90 viraram um só (são iguais), e o de 21,00 é outro: 2 itens.


6. order=True: fichas que sabem se ordenar

A dataclass comum escreve o __eq__, mas não o __lt__. Então o sorted dá o mesmo erro da aula anterior:

from dataclasses import dataclass


@dataclass
class Produto:
    nome: str
    preco: float


cesta = [Produto("Café", 18.9), Produto("Leite", 5.49)]
print(sorted(cesta))
$ python3 sem_order.py
Traceback (most recent call last):
  File "/home/ana/sem_order.py", line 11, in <module>
    print(sorted(cesta))
          ~~~~~~^^^^^^^
TypeError: '<' not supported between instances of 'Produto' and 'Produto'

Com order=True, o Python escreve as quatro comparações (<, <=, >, >=):

from dataclasses import dataclass


@dataclass(order=True)
class Produto:
    nome: str
    preco: float


cesta = [Produto("Leite", 5.49), Produto("Café", 18.9), Produto("Arroz", 27.5)]
for produto in sorted(cesta):
    print(produto)
print(Produto("Café", 18.9) < Produto("Leite", 5.49))
print(Produto("Café", 18.9) <= Produto("Café", 21.0))
$ python3 order.py
Produto(nome='Arroz', preco=27.5)
Produto(nome='Café', preco=18.9)
Produto(nome='Leite', preco=5.49)
True
True

Ordenou, mas pelo nome: Arroz, Café, Leite. A regra do order=True é comparar campo por campo, na ordem em que foram escritos, como se compara um dicionário de palavras: primeiro o nome; só se os nomes forem iguais, o preco desempata (foi o que aconteceu no último <=: dois cafés, e 18,9 é menor que 21,0).

Para ordenar pelo preço, o preço tem de ser o primeiro campo:

from dataclasses import dataclass


@dataclass(order=True)
class Produto:
    preco: float
    nome: str


cesta = [Produto(18.9, "Café"), Produto(5.49, "Leite"), Produto(27.5, "Arroz")]
for produto in sorted(cesta):
    print(produto)
$ python3 order_preco.py
Produto(preco=5.49, nome='Leite')
Produto(preco=18.9, nome='Café')
Produto(preco=27.5, nome='Arroz')

Do mais barato para o mais caro. Repare que, trocando a ordem dos campos, também mudou a ordem dos argumentos: agora é Produto(18.9, "Café"). Para não se confundir, use argumentos nomeados: Produto(nome="Café", preco=18.9) funciona em qualquer ordem.

Um campo que não entra na comparação

O estoque muda o tempo todo e não diz nada sobre qual produto é. Com field(compare=False), ele fica de fora do == e das comparações de ordem:

from dataclasses import dataclass, field


@dataclass(order=True)
class Produto:
    nome: str
    preco: float
    estoque: int = field(default=0, compare=False)


print(Produto("Café", 18.9, 12) == Produto("Café", 18.9, 3))
$ python3 compare_false.py
True

7. __post_init__: conferir os dados quando o objeto nasce

Como o @dataclass escreve o __init__, onde você põe a conferência do preço? Num método especial chamado __post_init__ ("depois do init"): o __init__ automático o chama logo depois de guardar os campos.

from dataclasses import dataclass


@dataclass
class Produto:
    nome: str
    preco: float

    def __post_init__(self):
        if self.preco <= 0:
            raise ValueError(f"preço inválido: {self.preco}")

    def descricao(self):
        return f"{self.nome}: R$ {self.preco:.2f}"


print(Produto("Café", 18.9).descricao())
Produto("Café", -5)
$ python3 post_init.py
Café: R$ 18.90
Traceback (most recent call last):
  File "/home/ana/post_init.py", line 18, in <module>
    Produto("Café", -5)
    ~~~~~~~^^^^^^^^^^^^
  File "<string>", line 5, in __init__
  File "/home/ana/post_init.py", line 11, in __post_init__
    raise ValueError(f"preço inválido: {self.preco}")
ValueError: preço inválido: -5

O rastro mostra o caminho: a linha 18 criou o produto, o __init__ escrito pelo @dataclass (de novo o <string>) chamou o seu __post_init__, e lá, na linha 11, o raise (aula raise) recusou o preço. Repare também que a dataclass aceitou um método comum, o descricao: ela é uma classe como as outras.


Formas diferentes de fazer a mesma coisa

A dataclass também se converte num dicionário com asdict, útil para gravar em JSON (aula JSON), e lista os próprios campos com fields:

from dataclasses import dataclass, asdict, fields


@dataclass
class Produto:
    nome: str
    preco: float
    estoque: int = 0


cafe = Produto("Café", 18.9, 12)
print(asdict(cafe))
print([campo.name for campo in fields(Produto)])
$ python3 asdict.py
{'nome': 'Café', 'preco': 18.9, 'estoque': 12}
['nome', 'preco', 'estoque']
Você quer guardar…Use
Dados com nome (produto, aluno, contato), com ou sem métodos@dataclass
Os mesmos dados, mas que não podem mudar (ou que vão para um conjunto)@dataclass(frozen=True)
Dados que precisam ser ordenados@dataclass(order=True), com o campo de ordenar primeiro (ou sorted(…, key=…))
Uma classe com muito comportamento e um __init__ especial (uma conta bancária que registra cada operação)Classe comum, com __init__ escrito à mão
Dados soltos, rápidos, sem classeUm dicionário ({"nome": "Café", "preco": 18.9})

Quando dá errado

Faltou um campo obrigatório
from dataclasses import dataclass


@dataclass
class Produto:
    nome: str
    preco: float


cafe = Produto("Café")
$ python3 faltou.py
Traceback (most recent call last):
  File "/home/ana/faltou.py", line 10, in <module>
    cafe = Produto("Café")
           ~~~~~~~^^^^^^^^
TypeError: Produto.__init__() missing 1 required positional argument: 'preco'

"Falta 1 argumento obrigatório: preco." É a mesma mensagem de um __init__ escrito à mão, porque o @dataclass escreveu um __init__ de verdade. Como consertar: passe o preço, ou dê um valor padrão ao campo.

Campo sem tipo: não é campo
from dataclasses import dataclass


@dataclass
class Produto:
    nome: str
    preco = 0.0


print(Produto("Café"))
cafe = Produto("Café", 18.9)
$ python3 sem_tipo.py
Produto(nome='Café')
Traceback (most recent call last):
  File "/home/ana/sem_tipo.py", line 11, in <module>
    cafe = Produto("Café", 18.9)
           ~~~~~~~^^^^^^^^^^^^^^
TypeError: Produto.__init__() takes 2 positional arguments but 3 were given

O print mostrou só o nome: o preco sumiu da ficha. Sem o : float, preco = 0.0 é só um atributo da classe comum, e o @dataclass não o enxerga como campo. Então o __init__ recebe só o self e o nome (2 argumentos), e o 18.9 sobrou ("recebe 2, foram passados 3"). Como consertar: todo campo leva o tipo: preco: float = 0.0.

Esqueceu o import
@dataclass
class Produto:
    nome: str
    preco: float
$ python3 sem_import.py
Traceback (most recent call last):
  File "/home/ana/sem_import.py", line 1, in <module>
    @dataclass
     ^^^^^^^^^
NameError: name 'dataclass' is not defined

O dataclass não vem pronto como o print; mora no módulo dataclasses. Como consertar: from dataclasses import dataclass no começo do arquivo. (Os outros erros desta aula, ValueError: mutable default, FrozenInstanceError e non-default argument … follows default argument, estão nas seções 3, 4 e 5.)


Referência rápida

EscritaO que faz
from dataclasses import dataclass, fieldTraz o decorador e a função de configurar campos
@dataclassEscreve __init__, __repr__ e __eq__
nome: strCampo obrigatório
estoque: int = 0Campo com valor padrão (sempre depois dos obrigatórios)
itens: list = field(default_factory=list)Uma lista nova para cada objeto (também dict, set)
field(compare=False)O campo não entra no == nem na ordem
@dataclass(frozen=True)Objeto imutável; pode ir para conjuntos
@dataclass(order=True)Escreve <, <=, >, >=, comparando campo por campo, na ordem escrita
def __post_init__(self):Roda logo depois do __init__ automático (para conferir dados)
replace(obj, campo=valor)Cópia do objeto com um campo trocado
asdict(obj)Converte num dicionário
SintomaCausa provável
ValueError: mutable default <class 'list'> for field … is not allowed: use default_factory= [] (ou {}, set()) como padrão: use field(default_factory=list)
TypeError: non-default argument 'x' follows default argument 'y'Campo obrigatório depois de um campo com padrão
dataclasses.FrozenInstanceError: cannot assign to field 'x'Mudou um campo de uma dataclass frozen=True: use replace
'<' not supported between instances of …Faltou order=True
Ordenou pelo campo erradoO order=True compara na ordem dos campos: ponha o campo de ordenar primeiro
Um campo some do print / takes 2 positional arguments but 3 were givenFaltou o tipo no campo (preco = 0.0 em vez de preco: float = 0.0)
NameError: name 'dataclass' is not definedFaltou o from dataclasses import dataclass

Exercícios

1. A ficha do contato

Complete a dataclass com o campo telefone, do tipo texto:

@dataclass
class Contato:
    nome: str
    telefone

2. Uma lista para cada um

Complete para que cada turma ganhe uma lista de alunos própria:

alunos: list = field()

3. Quais métodos o @dataclass (sem opções) escreve sozinho?

4. Por que itens: list = [] dá ValueError numa dataclass?

5. Com @dataclass(order=True) e os campos nome e preco, nessa ordem, o sorted ordena por…

6. cafe é de uma dataclass frozen=True. Como ter o café com preço 21.0?

Missão final: o ranking da turma

Missão: a professora quer a turma da maior nota para a menor, mas o sorted dá TypeError. Use order=True e faça a dataclass ordenar pela nota (dica: qual campo o order=True olha primeiro?). Os alunos são criados com argumentos nomeados, então você pode mexer na ordem dos campos à vontade.

from dataclasses import dataclass


@dataclass
class Aluno:
    nome: str
    nota: float


turma = [
    Aluno(nome="Bia", nota=8.5),
    Aluno(nome="Caio", nota=6.0),
    Aluno(nome="Duda", nota=9.25),
]
for aluno in sorted(turma, reverse=True):
    print(aluno.nome, aluno.nota)
from dataclasses import dataclass


@dataclass(order=True)
class Aluno:
    nota: float
    nome: str


turma = [
    Aluno(nome="Bia", nota=8.5),
    Aluno(nome="Caio", nota=6.0),
    Aluno(nome="Duda", nota=9.25),
]
for aluno in sorted(turma, reverse=True):
    print(aluno.nome, aluno.nota)

Se você só acrescentar o order=True, sem mexer nos campos, o programa roda, mas a ordem sai pelo nome ao contrário (Duda, Caio, Bia). No lab, a solução mostrou:

$ python3 main.py
Duda 9.25
Bia 8.5
Caio 6.0

Resumo

  • @dataclass (do módulo dataclasses) escreve __init__, __repr__ e __eq__ a partir dos campos nome: tipo.
  • O tipo é obrigatório para virar campo, mas não é conferido; para conferir, use __post_init__.
  • Campos com valor padrão vêm por último.
  • Lista, dicionário ou conjunto como padrão: field(default_factory=list). O = [] dá ValueError: mutable default … use default_factory, para evitar que todos dividam a mesma lista.
  • frozen=True congela o objeto (FrozenInstanceError ao mudar); para "mudar", crie outro com replace.
  • order=True permite ordenar, comparando campo por campo na ordem em que foram escritos.