class
, uma definição dinâmica usa uma classe embutida type
.
Metaclasse de tipo
A classe de tipo geralmente é usada para obter o tipo de um objeto. Por exemplo, assim:
h = "hello"
type(h)
<class 'str'>
Mas tem outros usos. Ele pode inicializar novos tipos.Como você sabe, tudo em Python é um objeto. Conclui-se que todas as definições têm tipos, incluindo classes e objetos. Por exemplo:
class A:
pass
type(A)
<class 'type'>
Pode não estar totalmente claro por que uma classe é atribuída a um tipo de classe
type
, ao contrário de suas instâncias:
a = A()
type(a)
<class '__main__.A'>
O objeto
a
é atribuído a uma classe como um tipo. É assim que o intérprete trata o objeto como uma instância da classe. A própria classe tem um tipo de classe type
porque o herda da classe base object
:
A.__bases__
(<class 'object'>,)
Tipo de aula
object
:
type(object)
<class 'type'>
A classe é
object
herdada por todas as classes por padrão, ou seja:
class A(object):
pass
Igual a:
class A:
pass
A classe sendo definida herda a classe base como um tipo. No entanto, isso não explica por que a classe base
object
é do tipo de classe type
. A questão é que type
é uma metaclasse. Como você já sabe, todas as classes herdam da classe base object
, que é do tipo metaclasse type
. Portanto, todas as classes também têm este tipo, incluindo a própria metaclasse type
:
type(type)
<class 'type'>
Este é o "ponto final da digitação" em Python. A cadeia de herança de tipo está fechada na classe
type
. A metaclasse type
serve como base para todas as classes em Python. É fácil verificar isso:
builtins = [list, dict, tuple]
for obj in builtins:
type(obj)
<class 'type'>
<class 'type'>
<class 'type'>
Uma classe é um tipo de dado abstrato e as instâncias dela têm uma referência de classe como um tipo.
Inicializando novos tipos com a classe de tipo
Ao verificar os tipos, a classe é
type
inicializada com um único argumento:
type(object) -> type
Ao fazer isso, ele retorna o tipo do objeto. No entanto, a classe implementa um método de inicialização diferente com três argumentos, que retorna um novo tipo:
type(name, bases, dict) -> new type
Parâmetros de inicialização de tipo
name
Uma string que define o nome da nova classe (tipo).bases
Uma tupla de classes básicas (classes que a nova classe herdará).dict
Dicionário com os atributos da futura classe. Normalmente com strings em chaves e tipos que podem ser chamados em valores.
Definição de classe dinâmica
Inicializamos a classe do novo tipo, fornecendo todos os argumentos necessários e a chamamos de:
MyClass = type("MyClass", (object, ), dict())
MyClass
<class '__main__.MyClass'>
Você pode trabalhar com a nova turma normalmente:
m = MyClass()
m
<__main__.MyClass object at 0x7f8b1d69acc0>
Além disso, o método é equivalente à definição de classe usual:
class MyClass:
pass
Definição dinâmica de atributos de classe
Há pouco sentido em uma classe vazia, então surge a pergunta: como adicionar atributos e métodos?
Para responder a esta pergunta, considere o código de inicialização inicial:
MyClass = type(“MyClass”, (object, ), dict())
Normalmente, os atributos são adicionados à classe no estágio de inicialização como o terceiro argumento - o dicionário. Você pode especificar nomes e valores de atributos no dicionário. Por exemplo, pode ser uma variável:
MyClass = type(“MyClass”, (object, ), dict(foo=“bar”)
m = MyClass()
m.foo
'bar'
Definição de método dinâmico
Objetos chamáveis também podem ser passados para o dicionário, por exemplo, métodos:
def foo(self):
return “bar”
MyClass = type(“MyClass”, (object, ), dict(foo=foo))
m = MyClass()
m.foo
'bar'
Este método tem uma desvantagem significativa - a necessidade de definir o método estaticamente (acho que, no contexto de tarefas de metaprogramação, isso pode ser considerado uma desvantagem). Além disso, definir um método com um parâmetro
self
fora do corpo da classe parece estranho. Então, vamos voltar à inicialização dinâmica de uma classe sem atributos:
MyClass = type(“MyClass”, (object, ), dict())
Depois de inicializar uma classe vazia, você pode adicionar métodos a ela dinamicamente, ou seja, sem uma definição estática explícita:
code = compile('def foo(self): print(“bar”)', "<string>", "exec")
compile
É uma função interna que compila o código-fonte em um objeto. O código pode ser executado por funções exec()
ou eval()
.
Parâmetros da função de compilação
- fonte O código- fonte
pode ser um link para um módulo. - filename
O nome do arquivo no qual o objeto será compilado. - modo
Se especificado"exec"
, a função compilará o código-fonte em um módulo.
O resultado do trabalho
compile
é um objeto de classe code
:
type(code)
<class 'code'>
O objeto
code
precisa ser convertido em um método. Como um método é uma função, começamos convertendo um code
objeto de classe em um objeto de classe function
. Para fazer isso, importe o módulo types
:
from types import FunctionType, MethodType
Vou importá-
MethodType
lo porque vou precisar dele mais tarde para converter a função em um método de classe.
function = FunctionType(code.co_consts[0], globals(), “foo”)
Parâmetros do método de inicialização FunctionType
code
Objeto de classecode
.code.co_consts[0]
É uma chamada para um descritor deco_consts
classecode
, que é uma tupla com constantes no código do objeto. Imagine um objetocode
como um módulo com uma única função que estamos tentando adicionar como um método de classe.0
É seu índice, pois é a única constante do módulo.globals()
Dicionário de variáveis globais.name
Parâmetro opcional que especifica o nome da função.
O resultado é uma função:
function
<function foo at 0x7fc79cb5ed90>
type(function)
<class 'function'>
Em seguida, você precisa adicionar esta função como um método de classe
MyClass
:
MyClass.foo = MethodType(function, MyClass)
Uma expressão bastante simples que atribui nossa função a um método de classe
MyClass
.
m = MyClass()
m.foo()
bar
Atenção
Em 99% dos casos, você pode sobreviver com definições de classe estáticas. No entanto, o conceito de metaprogramação é bom para revelar os aspectos internos do Python. Muito provavelmente, será difícil para você encontrar a aplicação dos métodos descritos aqui, embora em minha prática tenha existido tal caso.
Você já trabalhou com objetos dinâmicos? Talvez em outras línguas?