Metaclasses: programação além das classes

1. O que são Metaclasses e por que existem?

1.1. Definição fundamental: classes que criam classes

Em Python, tudo é objeto — inclusive classes. Se uma classe é um objeto, então ela precisa ser instância de algo. Esse "algo" é uma metaclasse. A metáfora clássica é: se uma classe é um molde para criar objetos, a metaclasse é o molde que cria o molde. Em outras palavras, metaclasses são classes cujas instâncias são classes.

1.2. A hierarquia de tipos em Python: type como a metaclasse padrão

Por padrão, toda classe em Python é instância de type. Quando escrevemos class MinhaClasse:, o Python chama type('MinhaClasse', (object,), {...}) por baixo dos panos. Isso significa que type é a metaclasse padrão de todas as classes.

class Exemplo:
    pass

print(type(Exemplo))           # <class 'type'>
print(type(Exemplo) is type)   # True
print(type(type))              # <class 'type'> — type é sua própria metaclasse

1.3. Diferença entre classe, instância e metaclasse

type (metaclasse)
  ↑
Classe (instância de type)
  ↑
Objeto (instância da classe)
  • Metaclasse: cria e configura classes
  • Classe: cria e configura objetos
  • Instância: o objeto final

2. Criando sua primeira Metaclasse

2.1. Sintaxe básica: herdando de type e sobrescrevendo __new__

Para criar uma metaclasse, herdamos de type e sobrescrevemos __new__ (ou __init__). __new__ é chamado antes da classe ser criada; __init__ depois.

class MinhaMeta(type):
    def __new__(mcs, name, bases, namespace):
        print(f"Criando classe: {name}")
        return super().__new__(mcs, name, bases, namespace)

class Cliente(metaclass=MinhaMeta):
    pass

# Saída: Criando classe: Cliente

2.2. O ciclo de vida de uma classe: __new__ vs __init__ na metaclasse

class MetaCiclo(type):
    def __new__(mcs, name, bases, namespace):
        print(f"__new__: {name}")
        namespace['_criada_por'] = 'MetaCiclo'
        return super().__new__(mcs, name, bases, namespace)

    def __init__(cls, name, bases, namespace):
        print(f"__init__: {name}")
        super().__init__(name, bases, namespace)

class Teste(metaclass=MetaCiclo):
    pass

print(Teste._criada_por)
# __new__: Teste
# __init__: Teste
# MetaCiclo

2.3. Exemplo prático: uma metaclasse que registra todas as classes criadas

class RegistroMetaclass(type):
    _registro = {}

    def __new__(mcs, name, bases, namespace):
        cls = super().__new__(mcs, name, bases, namespace)
        mcs._registro[name] = cls
        return cls

    @classmethod
    def listar_classes(mcs):
        return list(mcs._registro.keys())

class Animal(metaclass=RegistroMetaclass):
    pass

class Cachorro(Animal):
    pass

class Gato(Animal):
    pass

print(RegistroMetaclass.listar_classes())
# ['Animal', 'Cachorro', 'Gato']

3. Personalizando a Criação de Classes

3.1. Injeção automática de métodos e atributos

class InjetorMetaclass(type):
    def __new__(mcs, name, bases, namespace):
        namespace['versao'] = '1.0'
        namespace['info'] = lambda self: f"Classe {name}, versão {self.versao}"
        return super().__new__(mcs, name, bases, namespace)

class Produto(metaclass=InjetorMetaclass):
    pass

p = Produto()
print(p.info())  # Classe Produto, versão 1.0

3.2. Validação de definição de classe

class ValidadorNomes(type):
    def __new__(mcs, name, bases, namespace):
        if not name.startswith('Model'):
            raise TypeError(f"Nome '{name}' deve começar com 'Model'")
        return super().__new__(mcs, name, bases, namespace)

class ModelUsuario(metaclass=ValidadorNomes):
    pass  # OK

# class Usuario(metaclass=ValidadorNomes):  # TypeError!
#     pass

3.3. Modificando o namespace da classe antes da criação

class PrefixoMetaclass(type):
    def __new__(mcs, name, bases, namespace):
        novos_metodos = {}
        for attr, valor in namespace.items():
            if callable(valor) and not attr.startswith('__'):
                novo_nome = f"metodo_{attr}"
                novos_metodos[novo_nome] = valor
        namespace.update(novos_metodos)
        return super().__new__(mcs, name, bases, namespace)

class Acao(metaclass=PrefixoMetaclass):
    def correr(self):
        return "correndo"

a = Acao()
print(a.metodo_correr())  # correndo

4. Metaclasses vs Outras Abordagens

4.1. Comparação com decoradores de classe

# Decorador
def adicionar_versao(cls):
    cls.versao = '1.0'
    return cls

@adicionar_versao
class ViaDecorador:
    pass

# Metaclasse (já visto acima)
class ViaMetaclasse(metaclass=InjetorMetaclass):
    pass

Vantagens da metaclasse: aplica-se automaticamente a todas as subclasses; interfere no momento exato da criação.
Desvantagens: mais complexa; decoradores são mais explícitos e simples.

4.2. Quando usar metaclasse vs herança tradicional

Use metaclasse quando precisar modificar a estrutura da classe antes dela existir. Use herança quando quiser compartilhar comportamento entre instâncias.

4.3. O padrão "Template Method" implementado com metaclasses

class TemplateMetaclass(type):
    def __new__(mcs, name, bases, namespace):
        if 'executar' not in namespace:
            raise TypeError("Toda classe deve implementar 'executar'")
        return super().__new__(mcs, name, bases, namespace)

class PluginBase(metaclass=TemplateMetaclass):
    def executar(self):
        raise NotImplementedError

class PluginConcreto(PluginBase):
    def executar(self):
        return "Plugin executado"

5. Casos de Uso Reais e Frameworks

5.1. ORMs: como SQLAlchemy e Django usam metaclasses

SQLAlchemy usa metaclasses para transformar definições de classe em tabelas de banco de dados. Django faz algo similar com ModelBase, que converte atributos de classe em campos de banco.

# Exemplo conceitual similar ao Django
class ModelBase(type):
    def __new__(mcs, name, bases, namespace):
        campos = {k: v for k, v in namespace.items() if isinstance(v, Field)}
        namespace['_campos'] = campos
        return super().__new__(mcs, name, bases, namespace)

class Field:
    def __init__(self, tipo):
        self.tipo = tipo

class Model(metaclass=ModelBase):
    pass

class Usuario(Model):
    nome = Field(str)
    idade = Field(int)

print(Usuario._campos)  # {'nome': <Field>, 'idade': <Field>}

5.2. Singletons implementados via metaclasse

class SingletonMetaclass(type):
    _instancias = {}

    def __call__(cls, *args, **kwargs):
        if cls not in cls._instancias:
            cls._instancias[cls] = super().__call__(*args, **kwargs)
        return cls._instancias[cls]

class Configuracao(metaclass=SingletonMetaclass):
    def __init__(self):
        self.valor = 42

c1 = Configuracao()
c2 = Configuracao()
print(c1 is c2)  # True

5.3. Registro automático de plugins

class PluginRegistry(type):
    plugins = {}

    def __new__(mcs, name, bases, namespace):
        cls = super().__new__(mcs, name, bases, namespace)
        if not name.startswith('Base'):
            mcs.plugins[name] = cls
        return cls

class BasePlugin(metaclass=PluginRegistry):
    pass

class PluginAudio(BasePlugin):
    def reproduzir(self):
        return "Áudio"

class PluginVideo(BasePlugin):
    def reproduzir(self):
        return "Vídeo"

print(PluginRegistry.plugins)
# {'PluginAudio': <class ...>, 'PluginVideo': <class ...>}

6. Metaclasses e o Protocolo de Descritores

6.1. Interação entre metaclasses e descritores

Descritores são objetos que implementam __get__, __set__ ou __delete__. Metaclasses podem injetar descritores automaticamente.

6.2. Criando propriedades computadas em nível de metaclasse

class PropriedadeMetaclass(type):
    def __new__(mcs, name, bases, namespace):
        for attr, valor in namespace.items():
            if isinstance(valor, property):
                # Registra propriedades especiais
                pass
        return super().__new__(mcs, name, bases, namespace)

6.3. Exemplo: metaclasse que gera getters/setters automaticamente

class GetterSetterMeta(type):
    def __new__(mcs, name, bases, namespace):
        novos_atributos = {}
        for attr, valor in namespace.items():
            if attr.startswith('_') and not attr.startswith('__'):
                prop_name = attr[1:]
                novos_atributos[prop_name] = property(
                    lambda self, a=attr: getattr(self, a),
                    lambda self, val, a=attr: setattr(self, a, val)
                )
        namespace.update(novos_atributos)
        return super().__new__(mcs, name, bases, namespace)

class Pessoa(metaclass=GetterSetterMeta):
    def __init__(self):
        self._nome = "João"

p = Pessoa()
print(p.nome)   # João
p.nome = "Maria"
print(p.nome)   # Maria

7. Armadilhas, Boas Práticas e Performance

7.1. Complexidade e legibilidade

Metaclasses tornam o código mais difícil de entender. Use-as apenas quando necessário. Prefira decoradores ou __init_subclass__ para casos simples.

7.2. Problemas comuns: herança múltipla e conflitos

class MetaA(type): pass
class MetaB(type): pass

# class Conflito(MetaA, MetaB):  # TypeError: metaclass conflict
#     pass

# Solução: criar metaclasse que une ambas
class MetaUniao(MetaA, MetaB): pass
class Resolvido(metaclass=MetaUniao): pass  # OK

7.3. Impacto no desempenho

Metaclasses adicionam overhead na criação de classes, não na execução de métodos. Para a maioria dos casos, o impacto é irrelevante. Use timeit para medir se necessário.

8. O Futuro: Metaclasses e Python Moderno

8.1. Alternativas modernas: __init_subclass__ e decoradores

Desde Python 3.6, __init_subclass__ permite personalizar subclasses sem metaclasse:

class Base:
    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        cls.versao = '2.0'

class Derivada(Base):
    pass

print(Derivada.versao)  # 2.0

8.2. Metaclasses em frameworks assíncronos e tipagem

typing.Generic usa metaclasses para suportar parâmetros de tipo. Frameworks assíncronos como asyncpg usam metaclasses para gerar código eficiente.

8.3. Reflexão final

Metaclasses são uma ferramenta poderosa que permite programar "além das classes". Com grande poder vem grande responsabilidade: use metaclasses quando elas resolverem um problema real, não por "parecer elegante". Python moderno oferece alternativas mais simples como __init_subclass__ e decoradores, mas entender metaclasses aprofunda sua compreensão da linguagem como um todo.

Referências