Metaclasses são um dos tópicos mais fascinantes — e intimidantes — do Python. Muitos desenvolvedores passam anos programando em Python sem nunca precisar criar uma metaclass, mas entender como elas funcionam revela os mecanismos mais profundos da linguagem e abre portas para padrões de código extremamente elegantes e reutilizáveis.

Neste guia completo, você vai aprender o que são metaclasses, como o type funciona como metaclass padrão, como criar metaclasses personalizadas, e exemplos práticos de aplicações reais. Vamos desmistificar esse conceito de uma vez por todas!

O Que São Metaclasses?

Para entender metaclasses, primeiro precisamos entender o que são classes em Python. Diferente de linguagens como Java ou C++, onde classes são apenas constructs em tempo de compilação, em Python classes são objetos em tempo de execução. Sim: uma classe é um objeto assim como uma string, um número ou uma função.

Se uma classe é um objeto, então ela precisa ser criada por alguma coisa. Em Python, essa "alguma coisa" que cria classes é chamada de metaclass. Uma metaclass é uma classe cujas instâncias são classes. Ou, de forma mais simples: metaclasses são as "fábricas" que criam classes.

Assim como um objeto comum é uma instância de uma classe, uma classe é uma instância de uma metaclass. A metaclass padrão de todas as classes em Python é type.

# Uma string é instância de str
print(isinstance("hello", str))  # True

Uma classe é instância de type

class MinhaClasse: pass

print(isinstance(MinhaClasse, type)) # True print(type(MinhaClasse)) # <class 'type'>

Até mesmo type é instância de si mesmo!

print(isinstance(type, type)) # True

Essa capacidade de criar classes dinamicamente durante a execução é o que chamamos de metaprogramação. É um dos pilares que tornam frameworks como Django, SQLAlchemy e Pydantic tão poderosos.

A Hierarquia de Metaclasses

Vamos organizar visualmente a hierarquia:

  • Objetos comuns — instâncias de classes (ex: obj = MinhaClasse())
  • Classes — instâncias de metaclasses (ex: MinhaClasse é instância de type)
  • Metaclasses — classes cujas instâncias são classes (ex: type)
  • type — a metaclass raiz de todas as metaclasses, inclusive dela mesma

Essa hierarquia segue o padrão "tudo é um objeto" do Python. Até mesmo type é um objeto — uma instância de si mesmo. A documentação oficial sobre metaclasses explica essa hierarquia em detalhes.

type: A Metaclass Fundamental

O type é a metaclass padrão de todas as classes em Python. Você provavelmente já usou type() para verificar o tipo de um objeto, mas type tem uma segunda assinatura muito mais poderosa:

# type(nome, bases, dict) cria uma nova classe dinamicamente
MinhaClasse = type('MinhaClasse', (object,), {'x': 10})

obj = MinhaClasse() print(obj.x) # 10 print(type(obj)) # <class 'main.MinhaClasse'>

Os três parâmetros de type() são:

  • nome — string com o nome da classe
  • bases — tupla com as classes base (herança)
  • dict — dicionário com atributos e métodos da classe

As duas chamadas abaixo são equivalentes:

# Sintaxe com class
class Pessoa:
    especie = 'humano'
def saudacao(self):
    return f'Olá, sou {self.nome}'

Sintaxe com type

Pessoa = type('Pessoa', (object,), { 'especie': 'humano', 'saudacao': lambda self: f'Olá, sou {self.nome}' })

Essa equivalência é fundamental para entender metaclasses. Quando Python executa o bloco class, ele essencialmente chama a metaclass para criar a classe. A documentação da função type explica todas as assinaturas disponíveis.

Criando Sua Primeira Metaclass

Para criar uma metaclass personalizada, basta herdar de type e sobrescrever seus métodos. O método mais comum de se sobrescrever é __new__.

class MinhaMeta(type):
    def __new__(mcs, nome, bases, namespace):
        print(f'Criando classe: {nome}')
        namespace['criada_por'] = 'MinhaMeta'
        return super().__new__(mcs, nome, bases, namespace)

class Exemplo(metaclass=MinhaMeta): pass

print(Exemplo.criada_por) # MinhaMeta

Perceba que usamos metaclass=MinhaMeta na definição da classe. Esse parâmetro diz ao Python qual metaclass usar, em vez do type padrão.

Os Métodos de uma Metaclass

Os métodos mais importantes que você pode sobrescrever em uma metaclass são:

  • __new__(mcs, nome, bases, namespace) — chamado antes da classe ser criada. Deve retornar a nova classe.
  • __init__(cls, nome, bases, namespace) — chamado após a classe ser criada para inicializá-la.
  • __call__(cls, *args, **kwargs) — chamado quando a classe é invocada (ou seja, quando você cria uma instância).

O método __new__ da metaclass é onde a mágica realmente acontece. É ele que recebe o namespace da classe (todos os atributos e métodos definidos) e pode modificá-lo antes da classe ser criada.

class MetaValidator(type):
    def __new__(mcs, nome, bases, namespace):
        # Valida se métodos obrigatorios existem
        if nome != 'BaseValidator':
            if 'validar' not in namespace:
                raise TypeError(
                    f'{nome} deve implementar o método validar()'
                )
        return super().__new__(mcs, nome, bases, namespace)

class BaseValidator(metaclass=MetaValidator): pass

class ValidadorUsuario(BaseValidator): def validar(self, dados): return True # OK

class ValidadorIncompleto(BaseValidator): # Isso levanta TypeError!

pass

__init_subclass__: Alternativa Moderna

A partir do Python 3.6, a PEP 487 introduziu __init_subclass__, que oferece uma alternativa mais simples e elegante para muitos casos de uso que antes exigiam metaclasses. O método é chamado sempre que uma classe herda de outra.

class PluginBase:
    plugins = []
def __init_subclass__(cls, **kwargs):
    super().__init_subclass__(**kwargs)
    cls.plugins.append(cls)
    print(f'Plugin registrado: {cls.__name__}')

class PluginAudio(PluginBase): pass

class PluginVideo(PluginBase): pass

print(PluginBase.plugins)

[<class 'PluginAudio'>, <class 'PluginVideo'>]

O __init_subclass__ é chamado no momento da definição da classe filha, permitindo registrar, validar ou modificar o comportamento das subclasses sem precisar criar uma metaclass personalizada. A PEP 487 detalha a motivação e o funcionamento completo desse recurso.

Quando usar __init_subclass__ vs metaclass? Regra geral: se você só precisa executar código quando uma classe é herdada, __init_subclass__ é suficiente. Se você precisa interceptar ou modificar a criação da classe em si (incluindo classes que não herdam de ninguém), use metaclass.

O Método __call__ em Metaclasses

O método __call__ de uma metaclass é invocado sempre que uma instância da classe é criada. Isso permite interceptar o processo de criação de instâncias, sendo extremamente útil para implementar padrões como Singleton.

class SingletonMeta(type):
    _instancias = {}
def __call__(cls, *args, **kwargs):
    if cls not in cls._instancias:
        instancia = super().__call__(*args, **kwargs)
        cls._instancias[cls] = instancia
    return cls._instancias[cls]

class Configuracao(metaclass=SingletonMeta): def init(self): self.valor = 42

c1 = Configuracao() c2 = Configuracao() print(c1 is c2) # True print(c1.valor) # 42

Veja que __call__ na metaclass substitui o comportamento de Classe(). Antes de qualquer instância ser criada, a metaclass decide o que fazer. Esse padrão Singleton é amplamente discutido na comunidade e você pode encontrar mais exemplos no Stack Overflow sobre Singleton em Python.

Metaclasses com Argumentos

Assim como classes podem receber argumentos, metaclasses também podem. Isso é feito passando palavras-chave extras na declaração da classe:

class MetaComArgs(type):
    def __new__(mcs, nome, bases, namespace, **kwargs):
        print(f'Argumentos recebidos: {kwargs}')
        namespace['args_recebidos'] = kwargs
        return super().__new__(mcs, nome, bases, namespace)

class Servico(metaclass=MetaComArgs, cache=True, timeout=30): pass

print(Servico.args_recebidos) # {'cache': True, 'timeout': 30}

Para que isso funcione com herança, é necessário implementar também o __init_subclass__ ou garantir que os parâmetros sejam repassados corretamente. Essa técnica é usada por frameworks como o Django para configurar opções de modelos.

Metaclasses no Mundo Real

Metaclasses não são apenas um exercício acadêmico. Diversos frameworks e bibliotecas populares as utilizam para fornecer APIs elegantes e poderosas.

Django Models

O Django ORM usa metaclasses para transformar classes Python em tabelas de banco de dados. Quando você define um model, a metaclass ModelBase inspeciona os atributos da classe, registra os campos e configura o ORM.

from django.db import models

class Usuario(models.Model): nome = models.CharField(max_length=100) email = models.EmailField()

class Meta:
    db_table = 'usuarios'

A metaclass do Django traduz cada Field para uma coluna no banco, constrói o manager padrão (objects), e prepara toda a infraestrutura do ORM automaticamente.

SQLAlchemy ORM

O SQLAlchemy também usa metaclasses extensivamente para mapear classes para tabelas. A metaclass DeclarativeMeta é responsável por processar os atributos e construir o mapeamento objeto-relacional.

from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class Produto(Base): tablename = 'produtos' id = Column(Integer, primary_key=True) nome = Column(String(100))

Pydantic

O Pydantic, amplamente usado com FastAPI, usa metaclasses para validar tipos automaticamente durante a inicialização dos modelos.

from pydantic import BaseModel

class UsuarioPydantic(BaseModel): nome: str idade: int email: str

Validação automática na criação

usuario = UsuarioPydantic(nome="Ana", idade=25, email="[email protected]")

A metaclass do Pydantic analisa as anotações de tipo (nome: str) e gera automaticamente validadores para cada campo, além de fornecer métodos como dict() e json(). Mais detalhes na documentação oficial do Pydantic.

A documentação oficial do Django e a documentação do SQLAlchemy são ótimos recursos para entender como esses frameworks usam metaclasses na prática.

Registrando Subclasses Automaticamente

Um caso de uso prático para metaclasses é o registro automático de subclasses — um padrão comum em sistemas de plugins.

class PluginRegistry(type):
    registry = {}
def __new__(mcs, nome, bases, namespace):
    cls = super().__new__(mcs, nome, bases, namespace)
    if not namespace.get('abstract', False):
        mcs.registry[nome] = cls
    return cls

class Plugin(metaclass=PluginRegistry): abstract = True # Não registra a base

class PluginExportador(Plugin): def executar(self): return "Exportando dados..."

class PluginImportador(Plugin): def executar(self): return "Importando dados..."

print(PluginRegistry.registry)

{'PluginExportador': <class ...>, 'PluginImportador': <class ...>}

Esse padrão é usado por sistemas de plugins, frameworks de testes (como unittest, que registra automaticamente as classes de teste) e por muitos sistemas de registro de componentes.

Validação de Classes com Metaclasses

Outro uso comum é validar a estrutura das classes no momento da definição, garantindo que certos métodos existam ou que convenções de nomenclatura sejam seguidas.

class InterfaceValidator(type):
    def __new__(mcs, nome, bases, namespace):
        if nome.startswith('Abstract'):
            # Classes abstratas não precisam implementar tudo
            return super().__new__(mcs, nome, bases, namespace)
    obrigatorios = ['executar', 'cancelar']
    for metodo in obrigatorios:
        if metodo not in namespace:
            raise NotImplementedError(
                f'{nome} deve implementar {metodo}()'
            )

    # Convensão de nomenclatura: métodos públicos começam com letra minúscula
    for chave in namespace:
        if callable(namespace[chave]) and not chave.startswith('_'):
            if chave[0].isupper():
                raise NameError(
                    f'{nome}.{chave}() deve começar com letra minúscula'
                )

    return super().__new__(mcs, nome, bases, namespace)

class AbstractOperacao(metaclass=InterfaceValidator): pass

class OperacaoConcreta(AbstractOperacao): def executar(self): return "Executando"

def cancelar(self):
    return "Cancelando"

Essa validação em tempo de definição é muito mais eficiente do que descobrir erros apenas em tempo de execução. Frameworks como o módulo abc (Abstract Base Classes) da biblioteca padrão usam mecanismos similares.

Metaclasses e Descritores

Metaclasses podem trabalhar em conjunto com descritores (como @property) para criar APIs elegantes. Um exemplo clássico é a validação automática de atributos.

class Validado:
    def __init__(self, validador):
        self.validador = validador
        self.dados = {}
def __get__(self, obj, objtype=None):
    if obj is None:
        return self
    return self.dados.get(id(obj))

def __set__(self, obj, valor):
    valor_validado = self.validador(valor)
    self.dados[id(obj)] = valor_validado

class ModeloMeta(type): def new(mcs, nome, bases, namespace):

Converte validadores para descritores Validado

    for chave, valor in list(namespace.items()):
        if callable(valor) and hasattr(valor, '_validador'):
            namespace[chave] = Validado(valor)
    return super().__new__(mcs, nome, bases, namespace)

def validar_positivo(valor): if valor < 0: raise ValueError("Valor deve ser positivo") return valor validar_positivo._validador = True

def validar_string(valor): if not isinstance(valor, str): raise ValueError("Deve ser string") return valor validar_string._validador = True

class Produto(metaclass=ModeloMeta): preco = validar_positivo nome = validar_string

p = Produto() p.preco = 100 # OK

p.preco = -5 # ValueError: Valor deve ser positivo

print(p.preco) # 100

Metaclasses vs Decorators de Classe

Uma dúvida comum é: quando usar uma metaclass vs quando usar um decorator de classe? Ambos podem modificar classes, mas há diferenças importantes.

Decorators de classe são funções que recebem uma classe já criada e retornam uma versão modificada. Eles são executados após a classe ser criada. Exemplo famoso: @dataclass.

def adicionar_metodo(cls):
    cls.novo_metodo = lambda self: "Método adicionado"
    return cls

@adicionar_metodo class Exemplo: pass

print(Exemplo().novo_metodo()) # Método adicionado

Metaclasses interceptam a criação da classe antes dela existir, permitindo modificar o namespace, as bases e o próprio processo de construção.

Use decorators de classe para modificações simples e metaclasses quando você precisa:

  • Modificar o namespace antes da classe ser criada
  • Garantir que todas as subclasses de uma hierarquia sigam certas regras
  • Criar classes dinamicamente com base em configurações complexas
  • Implementar padrões como Singleton, Registry ou Interface Checking

Boas Práticas com Metaclasses

Aqui estão as principais recomendações ao trabalhar com metaclasses:

1. Prefira soluções mais simples primeiro

Antes de criar uma metaclass, pergunte-se se um decorator de classe, __init_subclass__ ou herança simples resolve o problema. Metaclasses adicionam complexidade e devem ser usadas com moderação.

2. Sempre chame super()

Nunca esqueça de chamar super().__new__() ou super().__init__() na sua metaclass. Pular isso quebrará a cadeia de herança de metaclasses.

3. Documente o propósito

Metaclasses não são intuitivas. Documente claramente o que sua metaclass faz e por que ela é necessária. Futuros mantenedores (incluindo você) agradecerão.

4. Use nomes descritivos

Por convenção, o primeiro parâmetro dos métodos da metaclass é chamado de mcs (metaclass) para diferenciar de cls (classe) e self (instância).

5. Considere o __init_subclass__

Para muitos casos que antes exigiam metaclasses (como registrar subclasses), o __init_subclass__ oferece uma alternativa mais simples e legível, como mostra a PEP 3115.

Metaclasses na Prática: Um Framework Pequeno

Vamos consolidar o aprendizado criando um mini framework de validação de dados usando metaclasses:

import json

class ValidatorMeta(type): def new(mcs, nome, bases, namespace): campos_validados = {} for chave, valor in namespace.items(): if isinstance(valor, type) and issubclass(valor, (int, str, float, bool)): campos_validados[chave] = valor namespace['_campos'] = campos_validados

    cls = super().__new__(mcs, nome, bases, namespace)

    if nome != 'Modelo':
        cls._validar_campos_obrigatorios()

    return cls

class Modelo(metaclass=ValidatorMeta): def to_dict(self): return {campo: getattr(self, campo) for campo in self._campos}

def to_json(self):
    return json.dumps(self.to_dict())

@classmethod
def _validar_campos_obrigatorios(cls):
    for campo in cls._campos:
        if not hasattr(cls, f'_{campo}'):
            # Cria um default para o campo
            setattr(cls, f'_{campo}', None)

def __init__(self, **kwargs):
    for campo, tipo in self._campos.items():
        if campo in kwargs:
            valor = kwargs[campo]
            if not isinstance(valor, tipo):
                raise TypeError(
                    f'{campo} deve ser {tipo.__name__}, '
                    f'recebeu {type(valor).__name__}'
                )
            setattr(self, f'_{campo}', valor)
        else:
            setattr(self, f'_{campo}', None)

def __getattr__(self, nome):
    if nome in self._campos:
        return getattr(self, f'_{nome}')
    raise AttributeError(f'{nome} não encontrado')

def __setattr__(self, nome, valor):
    if nome in self._campos:
        tipo = self._campos[nome]
        if not isinstance(valor, tipo):
            raise TypeError(
                f'{nome} deve ser {tipo.__name__}, '
                f'recebeu {type(valor).__name__}'
            )
    super().__setattr__(nome, valor)

class Usuario(Modelo): nome = str idade = int email = str

Usando o framework

user = Usuario(nome="Maria", idade=28, email="[email protected]") print(user.to_json()) # {"nome": "Maria", "idade": 28, "email": "[email protected]"}

user.idade = "trinta" # TypeError: idade deve ser int

Este exemplo mostra como metaclasses podem ser usadas para inspecionar a definição da classe (coletando campos com tipos) e gerar automaticamente comportamentos como validação de tipos, serialização para JSON e muito mais.

Depuração e Ferramentas

Trabalhar com metaclasses pode ser desafiador na hora de depurar. Aqui estão algumas dicas:

  • Use print() dentro do __new__ da metaclass para rastrear a criação de classes
  • Use type(cls) para verificar qual metaclass uma classe usa
  • Use cls.__mro__ para inspecionar a ordem de resolução de métodos
  • Ferramentas como pdb funcionam normalmente com metaclasses
class MinhaMeta(type):
    def __new__(mcs, nome, bases, namespace):
        print(f'[DEBUG] Criando {nome}')
        print(f'[DEBUG] Bases: {bases}')
        print(f'[DEBUG] Namespace: {list(namespace.keys())}')
        return super().__new__(mcs, nome, bases, namespace)

class Teste(metaclass=MinhaMeta): x = 1 def metodo(self): pass

Saída:

[DEBUG] Criando Teste

[DEBUG] Bases: (<class 'object'>,)

[DEBUG] Namespace: ['module', 'qualname', 'x', 'metodo']

Conclusão

Metaclasses são uma das ferramentas mais poderosas do Python. Elas permitem que você controle o próprio processo de criação de classes, abrindo possibilidades que seriam impossíveis ou extremamente trabalhosas em outras linguagens.

Vimos que:

  • type é a metaclass padrão de todas as classes
  • Metaclasses interceptam a criação de classes via __new__, __init__ e __call__
  • __init_subclass__ é uma alternativa moderna para muitos casos de uso
  • Frameworks como Django, SQLAlchemy e Pydantic usam metaclasses extensivamente
  • Padrões como Singleton, Registry e validação de interface são implementados com metaclasses

Para continuar seus estudos, confira nosso guia completo sobre Programação Orientada a Objetos em Python e explore como classes e objetos funcionam nos níveis mais profundos. Também recomendamos nosso artigo sobre Padrões de Projeto em Python, onde mostramos como aplicar metaclasses em patterns reais.

Lembre-se: com grandes poderes vêm grandes responsabilidades. Use metaclasses com sabedoria e seu código se tornará mais elegante, reutilizável e profissional!