make_dataclass decorator no Python é um recurso útil para quem cria classes dinamicamente e precisa aplicar uma transformação personalizada no momento em que a classe é construída. A função dataclasses.make_dataclass() já permitia montar dataclasses em tempo de execução, mas o parâmetro decorator amplia bastante esse fluxo: em vez de obrigatoriamente finalizar a classe com @dataclass, você pode escolher uma função compatível que receba a classe recém-criada e devolva a versão transformada.
Esse detalhe é especialmente interessante em geradores de modelos, bibliotecas de serialização, sistemas de plugins, ferramentas de linha de comando, clientes de API e aplicações que convertem esquemas externos em objetos Python. Ao longo deste guia, você verá como o parâmetro funciona, como escrever decorators seguros, quais cuidados tomar com validação e compatibilidade e quando uma classe declarada normalmente continua sendo a melhor escolha.
O que é make_dataclass?
make_dataclass() cria uma nova classe a partir de um nome, uma lista de campos, classes-base e opções semelhantes às aceitas por @dataclass. Em vez de escrever a classe estaticamente, você descreve sua estrutura usando dados.
from dataclasses import make_dataclass
Produto = make_dataclass(
"Produto",
[
("nome", str),
("preco", float),
("ativo", bool, True),
],
)
item = Produto("Curso de Python", 199.90)
print(item)
A classe resultante recebe automaticamente métodos como __init__, __repr__ e __eq__, conforme as opções selecionadas. O comportamento geral é o mesmo de uma dataclass comum, mas a definição nasce em tempo de execução.
Para que serve o parâmetro decorator?
O parâmetro decorator define qual função será responsável por transformar a classe criada. Essa função deve aceitar a classe como primeiro argumento e também precisa lidar com as opções de dataclass repassadas por palavra-chave.
from dataclasses import dataclass, make_dataclass
def meu_decorator(cls, **kwargs):
print(f"Decorando {cls.__name__}")
return dataclass(cls, **kwargs)
Cliente = make_dataclass(
"Cliente",
[("nome", str), ("email", str)],
decorator=meu_decorator,
)
O decorator acima apenas registra o nome da classe e delega a transformação final para dataclass(). Em um projeto real, ele poderia incluir validações, registrar a classe em um catálogo, adicionar metadados ou integrar uma biblioteca externa.
Exemplo com registro automático
Imagine um sistema que cria tipos a partir de uma especificação recebida de uma API. Você pode armazenar automaticamente cada classe em um registro central.
from dataclasses import dataclass, make_dataclass
REGISTRO = {}
def registrar_dataclass(cls, **kwargs):
transformada = dataclass(cls, **kwargs)
REGISTRO[transformada.__name__] = transformada
return transformada
Pedido = make_dataclass(
"Pedido",
[("id", int), ("total", float), ("status", str)],
decorator=registrar_dataclass,
slots=True,
)
print(REGISTRO["Pedido"])
Esse padrão evita uma segunda etapa de registro e mantém a criação da classe concentrada em uma única operação. Para sistemas maiores, o registro também pode armazenar versão do esquema, origem dos campos, regras de serialização e identificadores usados por plugins.
Validação de campos antes da transformação
Um decorator também pode inspecionar as anotações da classe antes de chamar dataclass(). Isso permite rejeitar estruturas inválidas cedo.
from dataclasses import dataclass, make_dataclass
TIPOS_PERMITIDOS = {str, int, float, bool}
def validar_tipos(cls, **kwargs):
invalidos = {
nome: tipo
for nome, tipo in cls.__annotations__.items()
if tipo not in TIPOS_PERMITIDOS
}
if invalidos:
raise TypeError(f"Tipos não permitidos: {invalidos}")
return dataclass(cls, **kwargs)
Configuracao = make_dataclass(
"Configuracao",
[("host", str), ("porta", int), ("debug", bool)],
decorator=validar_tipos,
)
Essa abordagem é útil quando a classe será construída com dados externos. Mesmo assim, não confunda validação da estrutura com validação dos valores em tempo de execução. Anotações de tipo não impedem que alguém passe um valor incompatível ao construtor; para isso, você ainda precisa de __post_init__, descriptors, bibliotecas de validação ou checagem explícita.
Integração com __post_init__
Você pode fornecer métodos no namespace da classe usando o argumento namespace.
from dataclasses import make_dataclass
def validar(self):
if self.preco < 0:
raise ValueError("O preço não pode ser negativo")
def apos_init(self):
validar(self)
Produto = make_dataclass(
"Produto",
[("nome", str), ("preco", float)],
namespace={"__post_init__": apos_init, "validar": validar},
)
O decorator personalizado pode complementar esse namespace, mas deve evitar sobrescrever métodos fornecidos pelo chamador sem uma regra clara. Em bibliotecas públicas, conflitos silenciosos tornam o comportamento difícil de depurar.
Cuidados com os argumentos recebidos
O decorator deve aceitar **kwargs porque make_dataclass() pode encaminhar opções como init, repr, eq, order, unsafe_hash, frozen, match_args, kw_only, slots e weakref_slot. Ignorar ou bloquear essas opções pode produzir erros inesperados.
from dataclasses import dataclass
def decorator_seguro(cls, **kwargs):
# faça inspeções ou registros aqui
return dataclass(cls, **kwargs)
Evite também chamar dataclass() duas vezes sobre a mesma classe sem necessidade. Embora algumas combinações possam parecer funcionar, decorar repetidamente pode gerar resultados difíceis de manter, especialmente com slots, herança ou métodos já existentes.
Compatibilidade e fallback
Antes de usar o parâmetro em uma biblioteca distribuída, defina claramente a versão mínima do Python. Em ambientes antigos, o argumento pode não existir. Um fallback razoável é criar a classe normalmente e depois aplicar o decorator manualmente.
from dataclasses import make_dataclass
campos = [("nome", str), ("valor", int)]
try:
Registro = make_dataclass(
"Registro",
campos,
decorator=registrar_dataclass,
)
except TypeError:
Registro = make_dataclass("Registro", campos)
REGISTRO[Registro.__name__] = Registro
Em código de produção, prefira verificar sys.version_info ou a assinatura disponível, em vez de capturar qualquer TypeError, pois um erro desse tipo também pode ser causado por outra parte da configuração.
Quando usar e quando evitar
Use make_dataclass() quando os campos realmente dependem de dados carregados em tempo de execução. Exemplos incluem esquemas JSON, tabelas descobertas dinamicamente, definições de formulários, plugins e modelos gerados por configuração. O parâmetro decorator faz sentido quando todas essas classes precisam passar pela mesma transformação adicional.
Evite geração dinâmica quando a estrutura é conhecida durante o desenvolvimento. Uma classe escrita normalmente oferece melhor navegação no editor, mensagens de erro mais claras, documentação mais simples e suporte superior de ferramentas estáticas. Se o objetivo é apenas reduzir repetição em poucas classes, uma função auxiliar ou uma classe-base pode ser mais previsível.
Testes recomendados
Teste a assinatura do construtor, os valores padrão, o comportamento com frozen=True, a criação com slots=True, a herança, o registro e as exceções produzidas pelo decorator. Também confirme que o objeto resultante continua compatível com dataclasses.fields(), asdict() e replace().
from dataclasses import fields, is_dataclass
assert is_dataclass(Pedido)
assert [campo.name for campo in fields(Pedido)] == ["id", "total", "status"]
assert REGISTRO["Pedido"] is Pedido
Boas práticas
Mantenha o decorator pequeno, determinístico e documentado. Não faça acesso à rede, gravação em banco ou outras operações lentas durante a criação da classe. Valide nomes e tipos vindos de fontes externas, não use eval() para converter anotações e evite registrar classes com nomes conflitantes sem uma política de versão.
Para aprofundar conceitos relacionados, veja também nossos guias sobre cópias de objetos no Python, introspecção com inspect, UUIDs no Python e ordenação de dependências com graphlib.
Como referências externas, consulte a documentação oficial de dataclasses e a PEP 557, que descreve a motivação e o modelo original das dataclasses.
Conclusão
O parâmetro decorator transforma make_dataclass() em um ponto de extensão mais flexível. Ele permite validar, registrar e adaptar classes dinâmicas sem separar a criação da transformação. A melhor implementação é simples: receba a classe, trate as opções de forma transparente, aplique a transformação necessária e devolva o tipo final. Com testes e limites claros, o recurso pode reduzir bastante a complexidade de geradores de modelos e sistemas baseados em esquemas.







