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 detype) - 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
pdbfuncionam 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!