typing.dataclass_transform permite que uma biblioteca informe ao analisador estático que um decorator, uma classe base ou uma metaclass cria comportamento semelhante ao de @dataclass. Frameworks que geram __init__, campos, comparação, ordenação ou modelos declarativos podem oferecer uma experiência de tipagem precisa sem exigir que o usuário aplique o decorator padrão de dataclasses.
O recurso não transforma classes por conta própria. Ele é um marcador para ferramentas estáticas. A biblioteca continua responsável por implementar o comportamento em runtime. Neste guia, você aprenderá a declarar transforms, criar decorators e bases, definir defaults, field specifiers, aliases, campos keyword-only, frozen, herança e testes.
O problema de classes geradas
def modelo(cls):
# gera __init__ dinamicamente
return cls
@modelo
class Usuario:
nome: str
idade: int
usuario = Usuario(nome="Ana", idade=30)Em runtime, o framework pode adicionar um construtor. Sem informação adicional, o analisador enxerga uma classe sem __init__ compatível e sinaliza a chamada como inválida.
Marcando o decorator
from typing import dataclass_transform
@dataclass_transform()
def modelo(cls):
return transformar_em_modelo(cls)Agora o analisador trata classes decoradas por @modelo como dataclass-like. Ele pode sintetizar o construtor com base nas anotações dos campos.
Runtime continua sendo responsabilidade da biblioteca
dataclass_transform não cria métodos. Se transformar_em_modelo() não gerar o __init__, o código falha em execução mesmo que o analisador aceite. O contrato estático e a implementação precisam permanecer alinhados.
Decorator tipado
from typing import TypeVar
T = TypeVar("T", bound=type)
@dataclass_transform()
def modelo(cls: T) -> T:
return transformar_em_modelo(cls)O decorator devolve a mesma classe em termos estáticos. Para APIs mais complexas, use parâmetros genéricos adequados e preserve metadados de runtime.
Classe base transformadora
@dataclass_transform()
class ModeloBase:
pass
class Produto(ModeloBase):
id: int
nome: str
produto = Produto(id=1, nome="Teclado")Todas as subclasses são tratadas como dataclass-like. Esse padrão é comum em ORMs, bibliotecas de validação e modelos de configuração.
Metaclass transformadora
@dataclass_transform()
class ModeloMeta(type):
...
class Modelo(metaclass=ModeloMeta):
...Uma metaclass pode coletar anotações, gerar métodos e registrar campos. O marcador informa ao analisador sobre esse comportamento.
eq_default
@dataclass_transform(eq_default=True)
def modelo(cls):
...eq_default informa se igualdade é gerada por padrão quando o usuário não especifica outra opção. Isso afeta a forma como ferramentas modelam métodos sintetizados.
order_default
@dataclass_transform(order_default=False)
def modelo(cls):
...O parâmetro descreve se métodos de ordenação são criados por padrão. A biblioteca deve oferecer comportamento correspondente em runtime.
kw_only_default
@dataclass_transform(kw_only_default=True)
class ModeloBase:
...Campos são tratados como keyword-only por padrão:
class Usuario(ModeloBase):
nome: str
idade: int
Usuario(nome="Ana", idade=30)
# Usuario("Ana", 30) deve ser rejeitadofrozen_default
Versões modernas permitem indicar se modelos são congelados por padrão. Isso influencia atribuições estáticas e relações de herança. A implementação precisa impedir mutação em runtime se promete frozen.
Field specifiers
Frameworks geralmente fornecem uma função de campo:
def campo(*, default=..., alias: str | None = None):
...Informe ao analisador que essa função possui semântica semelhante a dataclasses.field():
@dataclass_transform(field_specifiers=(campo,))
def modelo(cls):
...O checker pode interpretar defaults, factories, aliases, init e opções keyword-only conforme a assinatura do specifier.
Default e default_factory
class Carrinho(ModeloBase):
itens: list[str] = campo(default_factory=list)
aberto: bool = campo(default=True)A biblioteca precisa evitar defaults mutáveis compartilhados e executar factories por instância, assim como dataclasses.
Campos fora do __init__
class Registro(ModeloBase):
id: int = campo(init=False)
nome: strO analisador deve omitir id do construtor se o field specifier comunica init=False. O runtime deve preencher o campo por outro mecanismo.
Aliases de parâmetros
class Usuario(ModeloBase):
nome_completo: str = campo(alias="nome")Alguns frameworks aceitam Usuario(nome="Ana") embora o atributo seja nome_completo. Field specifiers podem comunicar aliases ao analisador quando o recurso é suportado.
Ordem de campos
Campos sem default geralmente devem aparecer antes de campos com default no construtor posicional. Bibliotecas que oferecem keyword-only podem flexibilizar a regra. Mantenha o comportamento estático igual ao runtime.
Herança
class Entidade(ModeloBase):
id: int
class Usuario(Entidade):
nome: strO analisador combina campos da base e da derivada. A ordem, defaults e frozen precisam respeitar as regras declaradas pelo framework.
Override de campos
Redefinir um campo em uma subclasse pode mudar tipo, default ou opções. O framework deve definir políticas claras. Mudanças incompatíveis podem quebrar substituição e construtores.
Classes congeladas e herança
Modelos frozen e não frozen possuem restrições de herança semelhantes às dataclasses. Não prometa imutabilidade estática e permita mutação silenciosa no runtime. Teste atribuições e construção em todas as combinações suportadas.
Decorators com argumentos
@dataclass_transform()
def modelo(*, frozen: bool = False, kw_only: bool = False):
def aplicar(cls):
return transformar(cls, frozen=frozen, kw_only=kw_only)
return aplicar
@modelo(frozen=True)
class Configuracao:
host: strPara o analisador entender flags, nomes e valores precisam seguir convenções reconhecidas. Use Literal[True] quando necessário para opções que afetam o tipo.
Assinaturas sobrecarregadas do decorator
Decorators que aceitam uso com e sem parênteses podem precisar de overloads:
@modelo
class A: ...
@modelo(frozen=True)
class B: ...Escreva uma API clara e teste as duas formas. A complexidade do decorator pode tornar as anotações difíceis; simplifique quando possível.
Comparação com @dataclass
Use @dataclass diretamente quando ele atende às necessidades. dataclass_transform é destinado a autores de frameworks que oferecem uma semântica própria, como validação, ORM, conversão, descriptors ou registro de campos.
Comparação com Protocol
Protocol descreve capacidades que um objeto já possui. dataclass_transform ensina ao analisador que uma ferramenta gera métodos e construtores. Os recursos podem coexistir, mas resolvem problemas diferentes.
Introspecção
O marcador pode deixar metadados como __dataclass_transform__ para ferramentas. Não confunda isso com dataclasses.is_dataclass(). Uma classe transformada por framework pode não ser uma dataclass real.
Geradores de schema
O framework deve manter sua própria representação de campos, defaults e validação. dataclass_transform melhora a experiência estática, mas não fornece APIs de serialização, schema ou reflexão.
Testes estáticos
from typing import assert_type
usuario = Usuario(nome="Ana", idade=30)
assert_type(usuario.nome, str)
assert_type(usuario.idade, int)Adicione casos que devem falhar: campo ausente, nome extra, tipo errado, argumento posicional proibido e mutação de frozen.
Testes de runtime
Execute a mesma matriz de casos em runtime. Uma divergência é perigosa: o analisador pode aceitar algo que falha ou rejeitar uma chamada válida. Teste assinaturas com inspect.signature(), criação, defaults, factories, igualdade e herança.
Compatibilidade de analisadores
Mypy e pyright podem implementar detalhes em ritmos diferentes. Consulte a especificação, evite extensões não portáveis e execute uma suíte nos analisadores suportados.
Compatibilidade de versões
Use typing_extensions.dataclass_transform em versões anteriores. O decorator pode ser importado da extensão sem alterar o comportamento de runtime da biblioteca.
Erros comuns
- Achar que o marcador gera métodos: a biblioteca ainda precisa implementá-los.
- Prometer defaults diferentes do runtime: o construtor fica divergente.
- Esquecer field_specifiers: o analisador não entende sua função de campo.
- Não testar herança: ordem e frozen podem quebrar.
- Criar decorator excessivamente dinâmico: ferramentas não conseguem modelá-lo.
- Usar quando @dataclass bastaria: aumenta manutenção sem benefício.
Exemplo completo: mini framework
from dataclasses import dataclass, field
from typing import dataclass_transform
def atributo(*, default=..., default_factory=...):
if default_factory is not ...:
return field(default_factory=default_factory)
if default is not ...:
return field(default=default)
return field()
@dataclass_transform(field_specifiers=(atributo,))
def modelo(cls=None, *, frozen: bool = False):
def aplicar(alvo):
return dataclass(alvo, frozen=frozen)
if cls is None:
return aplicar
return aplicar(cls)
@modelo(frozen=True)
class Produto:
id: int
nome: str
tags: list[str] = atributo(default_factory=list)
produto = Produto(id=1, nome="Teclado")O runtime delega à dataclass real; o marcador comunica a semântica do decorator customizado ao analisador. Frameworks reais podem adicionar validação, aliases e conversão.
Quando usar
Use dataclass_transform se você é autor de uma biblioteca que gera construtores e campos a partir de anotações. Usuários comuns raramente precisam aplicá-lo diretamente. Para modelos de aplicação, dataclass, attrs, Pydantic ou classes normais geralmente são suficientes.
Conclusão
dataclass_transform cria uma ponte entre frameworks declarativos e analisadores estáticos. Ele descreve métodos e parâmetros sintetizados, defaults, fields, keyword-only e frozen sem impor uma implementação específica.
A documentação oficial de dataclass_transform no módulo typing detalha os parâmetros. Mantenha a implementação de runtime alinhada ao contrato, declare field specifiers e proteja a experiência do usuário com testes estáticos e de execução.







