Entendendo Metaclasses em Orientação a Objetos com Python

Entendendo Metaclasses em Orientação a Objetos com Python

Aplicação do método type

1. Verificando tipos de dados


texto = 'olá mundo'  # str()
lista = [11, 22, 33, 44]  # list()
dicionario = {'nome': 'joao', 'senha': 123}  # dict()
tupla = (11, 22, 33, 44)  # tuple()
print(type(texto))  # <class>
print(type(lista))  # <class>
print(type(dicionario))  # <class>
print(type(tupla))  # <class>
</class></class></class></class>

2. Verificando qual classe produziu um objeto


class Aluno:
    pass
objeto = Aluno()
print(type(objeto))  # <class>
</class>

3. Classes também são objetos, produzidos por quem?


class Aluno:
    pass
objeto = Aluno()
print(type(objeto))  # <class>
print(type(Aluno))  # <class>
class A:pass
class B:pass
print(type(A), type(B))  # <class> <class>
</class></class></class></class>

Descobrimos que nossas classes são todas produzidas pelo type. E o type é o produtor de todas as classes (incluindo ele mesmo) e é conhecido como **metaclass**. ```

print(type(type)) #


Mecanismo de criação de classes na base
---------------------------------------

Normalmente usamos a palavra-chave class para criar classes: ```

class NomeDaClasse(classe_pai):
    código_corpo_classe

Se examinarmos o código fonte do type, podemos ver que além de receber um objeto para determinar sua classe, ele tem outro uso: Receber o nome da classe, classes pai e o namespace do corpo para criar uma nova classe. ```

corpo_classe = """ nome = 'maria' """ namespace_classe = {}

exec executa o código e insere alguns nomes no namespace

exec(corpo_classe, {}, namespace_classe) cls = type('Estudante', (object,), namespace_classe) # nome da classe, classes pai, namespace obj = cls() print(obj.class) # print(obj.nome) # maria


Este processo é complicado e ainda tem diferenças em relação à implementação da palavra-chave class, então aqui apenas para conhecimento. Na verdade, nossas classes são todas produzidas por metaclasses, e na base é usado um método type. Controlando a criação de classes através de metaclasses
-------------------------------------------------------

Como as classes são produzidas por metaclasses, podemos entender que as classes são na verdade objetos instanciados pela metaclass, Analogia: - Objeto -- produzido por Classe() -- dispara o `\_\_init\_\_` no corpo da classe, obtendo atributos exclusivos - Classe -- produzida por metaclass -- dispara o `\_\_init\_\_` na metaclass, obtendo atributos de classe exclusivos para cada classe Portanto, podemos controlar o processo de criação da classe derivando o método `\_\_init\_\_` da metaclass. A derivação naturalmente usa herança e a palavra-chave super, tornando a classe que tem type como classe pai também uma metaclass, pois tem o método de criar classes. ```

class MinhaMetaClasse(type):
    def __init__(cls, nome, bases=None, namespace=None):
        if not nome.istitle():  # Se o nome da classe não começar com maiúscula
            raise TypeError(f'Nome de classe deve começar com maiúscula, você escreveu {nome}')  # Erro para parar o programa
        # Se seguir o estilo de nome que começa com maiúscula, continue executando
        super().__init__(nome, bases, namespace)  # Herdar métodos da classe pai

class A(metaclass=MinhaMetaClasse):  # Modifica a metaclass padrão para nossa metaclass, também modifica a forma de criar esta classe
    pass
class bbb(metaclass=MinhaMetaClasse):  # Erro aqui, TypeError: Nome de classe deve começar com maiúscula, você escreveu bbb
    pass

Se escolhermos modificar a metaclass de criação de classe (através da modificação do parâmetro palavra-chave metaclass), essa metaclass pode ser derivada de type, e através da derivação do duplo init de type podemos controlar o processo de criação da classe. ### Controlando o processo de criação de classes através do método \_\_new\_\_

Na verdade, quando uma classe é criada, a sintaxe acionada é: type(nome_classe, bases, dicionário) Similarmente, quando um objeto é criado, também é chamando a classe, o que dispara o \_\_call\_\_() na metaclass. A criação da classe passa por três etapas: 1. Pelo método \_\_new\_\_ da metaclass, cria um novo objeto (a classe também é um objeto) 2. Chama o método \_\_init\_\_ da metaclass para adicionar atributos exclusivos ao objeto 3. Retorna a classe criada Então a função type passa por um código semelhante ao seguinte: ```

O código a seguir apenas descreve brevemente o type, tem diferenças com o código fonte, lista apenas algumas características.

def type(nome_classe, bases, dicionario): # 1. Chama o new do type para produzir uma nova classe cls = type.new(mcs, nome_classe, bases, dicionario) # Passa metaclass, nome, classes pai, namespace # 2. Chama o duplo init para dar à nova classe alguns atributos específicos type.init(cls, nome_classe, bases=None, dicionario=None) # Passa a nova classe, nome, classes pai, namespace # 3. Retorna a classe return cls


Portanto, além de controlar através do duplo init, podemos controlar o processo de criação da classe derivando o duplo new. (Qualquer método deve especificar a metaclass como a que escrevemos herdada de type) Além disso, modificar o processo de criação da classe derivando o duplo new tem um impacto mais profundo, porque o método new passará o mcs metaclass, este atributo de metaclass irá procurar nas classes pai, então o método new pode não só controlar a classe produzida, mas também controlar o processo das subclasses da classe produzida. Em comparação, o duplo init só pode modificar o processo de classes criadas por esta metaclass, porque o método init só passará a nova classe e alguns atributos de classe, não possuindo o atributo de metaclass. Para lembrar de forma figurativa, o duplo new é a modificação genética, o duplo init é a cirurgia plástica pós-natal. #### Controlando que todas as propriedades de string de subclasses de uma classe sejam agrupadas no dicionário words

class MinhaMetaClasse(type): def new(mcs, nome_classe, classes_pai, atributos_classe): if nome_classe == 'Modelos': return type.new(mcs, nome_classe, classes_pai, atributos_classe) palavras = {} for k, v in atributos_classe.items(): # Pega todos os atributos de campo da tabela if isinstance(v, str): # Se o valor de um atributo for do tipo string, agrupa em palavras palavras[k] = v for k in palavras: atributos_classe.pop(k) # Remove os atributos do tipo string atributos_classe['palavras'] = palavras return type.new(mcs, nome_classe, classes_pai, atributos_classe)

class Modelos(metaclass=MinhaMetaClasse): # Classe pass

class Joao(Modelos): # Subclasse nome = 'joao' idade = 25 hobby = 'programar'

print(Joao.idade) # 25 print(Joao.palavras)

{'module': 'main', 'qualname': 'Joao', 'nome': 'joao', 'hobby': 'programar'}

print(Joao.nome) # Erro, porque nome não é mais um atributo desta classe


Controlando a criação de objetos por metaclasses
------------------------------------------------

O título parece semelhante, mas note a diferença. A seção anterior se refere ao controle do processo de criação da classe, esta seção explica o processo de criação de objetos. - No post anterior, mencionamos que o método mágico `\_\_call\_\_` da classe é disparado automaticamente quando um objeto é chamado com parênteses. - Então, considerando a classe como um objeto, o `\_\_call\_\_` em sua metaclass também será disparado automaticamente quando o nome da classe é chamado com parênteses. Ou seja, o processo de criação de objetos que estamos acostumados, `obj = NomeDaClasse()`, dispara automaticamente o `\_\_call\_\_` na metaclass. Entendendo este ponto, podemos tentar modificar o `\_\_call\_\_` da metaclass para controlar a criação de objetos. Podemos modificar diretamente por derivação ou sobrescrever um call. Geralmente, a metaclass de uma classe é type, e seu `\_\_call\_\_` faz principalmente as seguintes coisas: ```

class MinhaMetaClasse(type):
    def __call__(cls, *args, **kwargs):
        # 1. Cria um objeto vazio
        obj = cls.__new__(cls)
        # 2. Chama o init da classe passando o objeto e os argumentos
        cls.__init__(obj, *args, **kwargs)
        # 3. Retorna o objeto criado
        return obj
# O __call__ acima é apenas uma imitação simples do type, é melhor usar super para derivar

class A(metaclass=MinhaMetaClasse):  # Modifica a metaclass padrão para nossa metaclass, também modifica a forma de criar esta classe
    pass


obj = A()  # Dispara o __call__ do MinhaMetaClasse
print(obj)  # <__main__.A object at 0x0000025DBB436610>

Permitindo apenas argumentos de palavra-chave na criação de objetos


# Método de implementação 1
class MinhaMetaClasse(type):
    def __call__(cls, *args, **kwargs):
        if args:
            raise TypeError('Você só pode passar argumentos de palavra-chave')  # Controla aqui para permitir apenas argumentos de palavra-chave
        return super().__call__(*args, **kwargs)  # Passa automaticamente o objeto chamado, usando o método da classe pai
		# E retorna o valor, mantendo a estrutura consistente com o call original

class A(metaclass=MinhaMetaClasse):  # Modifica a metaclass padrão para nossa metaclass, também modifica a forma de criar esta classe
    def __init__(self, nome):
        self.nome = nome


obj = A('joao')
print(obj.nome)

# Método de implementação 2 (não recomendado)
class MinhaMetaClasse(type):
    def __call__(cls, *args, **kwargs):
        # 1. Cria um objeto vazio
        obj = cls.__new__(cls)
        # 2. Chama o init da classe passando o objeto e os argumentos
        if args:
            raise TypeError('Você só pode passar argumentos de palavra-chave')  # Controla aqui para permitir apenas argumentos de palavra-chave
        cls.__init__(obj, *args, **kwargs)
        # 3. Retorna o objeto criado
        return obj

Tags: Python metaclasses Orientação a Objetos programação python type

Publicado em 7-9 18:19