2
respostas

O que siginifica esse atributo, metaclass?

Boa tarde,

Durante as aulas do professor Luan no curso de Python 3 - OO, ficou meio obscuro para mim, a questão das classes abstratas? Poderia explicar melhor? O que significa o atributo metaclass?

Em minhas pesquisas eu notei que o helper ABCMeta é a classe que defini que minha classe será abstrata, certo?

Obrigado

2 respostas

Thiago,

Em orientação a objetos, uma metaclasse é uma classe cujas instâncias também são classes e não objetos no sentido tradicional. Assim como classes definem o comportamento de certos objetos, metaclasses definem o comportamento de certas classes e suas instâncias.

A princípio, poderíamos criar objetos diretamente, sem intermediários: apenas defina um objeto X, dê para ele um conjunto de propriedades A, B, C e um conjunto de operações P, Q, R. Linguagens que não possuem o conceito de "classe" (como JavaScript) fazem isso desse jeito:

// Construção direta
var x = new Object();
x.foo = "bar";
x.baz = function() { ... };

// Literal para objetos
var x = {
    foo:"bar",
    baz:function() { ... }
};

// Função de criação
var x = Object.create(null, {
    foo:"bar",
    baz:function() { ... }
});

// Construtor
function X() {
    this.foo = "bar";
    this.baz = function() { ... };
}
var x = new X();

Entretanto, muitas vezes queremos criar vários objetos "parecidos". Uma opção é usar uma função construtora. Outra é criar um objeto especial para descrever um conjunto de características que todo objeto definido dessa forma deve possuir. Esse objeto é chamado de classe.

class X {
    private String foo = "bar";
    public void baz() { ... }
}

Object x 

As vantagens das classes são muitas. Além de ser uma forma declarativa de se expressar a estrutura do objeto, ela também constitui um tipo que relaciona todos os objetos criados por ela em um único conjunto, além de estabelecer relações de subtipo entre ela e outras classes.

Se a classe também é um objeto[1], como se cria uma classe? Da mesma maneira que se criam outros objetos: ou via um literal/palavra-chave da própria linguagem (mais comum), ou via uma função construtora, ou criando-se uma instância de uma metaclasse.

# Literal
class X(object):
    def __init__(self):
        self.foo = "bar"
    def baz():
        ...

# Instância de metaclasse
def init(self):
    self.foo = "bar"
def baz():
    ...
X = type("X",       # Nome da classe
         (object,), # Herda de...
         {          # Atributos e operações
             "__init__":init,
             "baz":baz,
         })

# Função construtora
def constuirX():
    X = ... # Usa um dos métodos acima - ou uma lógica mais complexa - pra definir a classe
    return X

O primeiro e o segundo exemplo são equivalentes. Nesse exemplo (Python), type é a classe da classe X, i.e. sua metaclasse. Toda classe criada usando a palavra-chave class possui por padrão a metaclasse type, a menos que definida de outra forma.

>>> x = X()
>>> x.__class__
<class '__main__.X'>
>>> x.__class__.__class__
<type 'type'>

Nota: Nem toda linguagem considera classes como objetos (i.e. não são "membros de primeira classe"), de modo que não é possível criá-las ou modificá-las após a compilação. Entretanto, a grande maioria das linguagens modernas possui no mínimo mecanismos para se inspecionar programaticamente a estrutura das classes e, assim, manipular de forma genérica os objetos criados pelas mesmas (reflexão)

continua...

... Segunda parte:

Pra que serve? Em geral, quando queremos criar uma classe não somos muito exigentes: pedimos que os objetos criados por ela tenha um conjunto de atributos do mesmo tipo, umas operações em comum, e só. Por essa razão, o "comportamento padrão" ao se criar classes é apenas fazer essas definições e pronto. A metaclasse default (type em Python, Class em Ruby[2]), portanto, faz justamente isso.

Entretanto, há casos em que se quer que um conjunto de classes tenha várias coisas em comum. Por exemplo ao se criar um framework com um propósito específico, demandando que toda classe defina as mesmas coisas de novo e de novo (ex.: Django, que implementa o mapeamento objeto-relacional através de uma classe por tabela do BD). Sem metaclasses, seria necessário que o programador definisse toda a funcionalidade comum em cada classe criada. Com elas, pode-se deixar a metaclasse cuidar da criação de fato das classes, e deixar que o programador se concentre somente nos detalhes que interessa a ele.

class Funcionario(Model):
    nome = CharField(max_length=30)
    empresa = ForeignKey(Empresa)

fulano = Funcionario(nome="Fulano", empresa_id=10) 
# A metaclasse definiu o construtor pra nós

isinstance(fulano.nome, CharField) # False
isinstance(fulano.nome, unicode) # True

No exemplo acima (que usa metaclasses indiretamente, ao se herdar de Model), o que se está sendo definido não são os atributos dos objetos criados pela classe Funcionario, e sim regras para mapear atributos simples (ex.: nome é do tipo string, não CharField) a colunas do BD. A estrutura das instâncias de Funcionario em si são inferidas pela metaclasse a partir das informações apresentadas.

fulano.nome.__class__       # unicode (string)
fulano.empresa.__class__    # Empresa (outro Model)
fulano.epresa_id.__class__  # int (o ID da empresa no BD)
fulano.id.__class__         # int (o ID do funcionário no BD)

Como e quando deve ser usada? Raramente. Praticamente nunca. A menos que você esteja trabalhando em uma framework ou biblioteca genérica - destinada a serem usadas por outros programadores, que irão criar suas próprias classes nela - dificilmente existirá uma situação em que metaclasses são necessárias. Pessoalmente, nunca as utilizei, e tenho dificuldade sequer em imaginar situações em que elas são necessárias. Por isso, sugiro fazer a si mesmo as seguintes perguntas:

Estou criando muitas classes? Essas classes têm muitas coisas em comum? E ainda assim não é um caso de herança/mixin? Essa parte comum é redundante? (i.e. pode ser inferida a partir da parte não-comum) O computador poderia fazer esse trabalho redundante pra mim? Se a resposta a essas perguntas for "sim", talvez valha a pena usar metaclasses no seu caso. Sobre como usá-las:

Imagine a "classe ideal" (i.e. o que você, como programador, iria ter que incluir na definição da classe, toda vez; deixando de fora toda parte redundante); Estabeleça os passos que seriam necessários para transformar essa classe ideal na classe "real" (i.e. aquela que o programa precisa para funcionar - que defina os atributos e operações dos objetos tal como eles devem ter de fato); Implemente uma metaclasse que realize essa transformação. Receba uma definição de classe como parâmetro; Interprete sua estrutura (se a linguagem dá suporte a metaclasses, isso deve ser trivial); Adicione/remova/altere sua estrutura segundo a lógica estabelecida. Só para não deixar sem exemplo, vou propor uma alternativa à resposta do Rogers Corrêa: nossa "classe ideal" não deveria ter um construtor atribuindo cada atributo posicional ao self (pois seria redundante - essa informação já está presente no slots). Mas a classe real precisa. Vamos então transformar a classe ideal na classe real mantendo todo o resto intacto:

# Definindo uma metaclasse na forma de uma função
def minha_metaclass(nome, parents, atributos):
    # Acha os slots da classe
    slots = atributos['__slots__']

    # Cria um novo construtor que usa esses slots
    def init(self, *args):
        for i in range(len(slots)):
            setattr(self, slots[i], args[i])

    # Acrescenta esse construtor na definição da classe
    atributos['__init__'] = init

    # Cria a classe de fato (usando type)
    return type(nome, parents, atributos)

class Carro(object):
    __metaclass__ = minha_metaclass
    __slots__ = ['marca', 'modelo', 'ano', 'cor']

c = Carro('Toyota', 'Prius', 2005, 'green')
c.marca  # Toyota
c.modelo # Prius
c.ano    # 2005
c.cor    # green

Nota: Em Ruby, toda classe é uma instância de Class, enquanto em Python as classes podem ser instâncias de qualquer classe que herde de type. Não tenho experiência com Ruby para comentar sobre o funcionamento de metaclasses nessa linguagem.

Para saber mais metaclasses, indico o excelente livro: Python Fluente - Luciano Ramalho --> https://novatec.com.br/livros/pythonfluente/