class
, una definición dinámica usa una clase en línea type
.
Tipo metaclase
La clase de tipo se usa a menudo para obtener el tipo de un objeto. Por ejemplo así:
h = "hello"
type(h)
<class 'str'>
Pero tiene otros usos. Puede inicializar nuevos tipos.Como sabes, todo en Python es un objeto. De ello se deduce que todas las definiciones tienen tipos, incluidas clases y objetos. Por ejemplo:
class A:
pass
type(A)
<class 'type'>
Puede que no quede del todo claro por qué a una clase se le asigna un tipo de clase
type
, a diferencia de sus instancias:
a = A()
type(a)
<class '__main__.A'>
Al objeto
a
se le asigna una clase como tipo. Así es como el intérprete trata el objeto como una instancia de la clase. La clase en sí tiene un tipo de clase type
porque lo hereda de la clase base object
:
A.__bases__
(<class 'object'>,)
Tipo de clase
object
:
type(object)
<class 'type'>
La clase es
object
heredada por todas las clases de forma predeterminada, es decir:
class A(object):
pass
Igual que:
class A:
pass
La clase que se define hereda la clase base como tipo. Sin embargo, esto no explica por qué la clase base
object
es del tipo clase type
. El punto es que type
es una metaclase. Como ya sabe, todas las clases heredan de la clase base object
, que es del tipo metaclase type
. Por tanto, todas las clases también tienen este tipo, incluida la propia metaclase type
:
type(type)
<class 'type'>
Este es el "punto final de la escritura" en Python. La cadena de herencia de tipos está cerrada en la clase
type
. La metaclase type
sirve como base para todas las clases en Python. Es fácil verificar esto:
builtins = [list, dict, tuple]
for obj in builtins:
type(obj)
<class 'type'>
<class 'type'>
<class 'type'>
Una clase es un tipo de datos abstracto y sus instancias tienen una referencia de clase como tipo.
Inicializando nuevos tipos con la clase de tipos
Al verificar los tipos, la clase se
type
inicializa con un solo argumento:
type(object) -> type
Al hacerlo, devuelve el tipo de objeto. Sin embargo, la clase implementa un método de inicialización diferente con tres argumentos, que devuelve un nuevo tipo:
type(name, bases, dict) -> new type
Tipo de parámetros de inicialización
name
Una cadena que define el nombre de la nueva clase (tipo).bases
Una tupla de clases base (clases que heredará la nueva clase).dict
Diccionario con los atributos de la clase futura. Por lo general, con cadenas en claves y tipos invocables en valores.
Definición de clase dinámica
Inicializamos la clase del nuevo tipo, proporcionando todos los argumentos necesarios y la llamamos:
MyClass = type("MyClass", (object, ), dict())
MyClass
<class '__main__.MyClass'>
Puede trabajar con la nueva clase como de costumbre:
m = MyClass()
m
<__main__.MyClass object at 0x7f8b1d69acc0>
Además, el método es equivalente a la definición de clase habitual:
class MyClass:
pass
Definición dinámica de atributos de clase
No tiene mucho sentido una clase vacía, por lo que surge la pregunta: ¿cómo agregar atributos y métodos?
Para responder a esta pregunta, considere el código de inicialización inicial:
MyClass = type(“MyClass”, (object, ), dict())
Normalmente, los atributos se agregan a la clase en la etapa de inicialización como tercer argumento: el diccionario. Puede especificar nombres y valores de atributos en el diccionario. Por ejemplo, podría ser una variable:
MyClass = type(“MyClass”, (object, ), dict(foo=“bar”)
m = MyClass()
m.foo
'bar'
Definición de método dinámico
Los objetos invocables también se pueden pasar al diccionario, por ejemplo, métodos:
def foo(self):
return “bar”
MyClass = type(“MyClass”, (object, ), dict(foo=foo))
m = MyClass()
m.foo
'bar'
Este método tiene un inconveniente importante: la necesidad de definir el método de forma estática (creo que en el contexto de las tareas de metaprogramación, esto puede considerarse un inconveniente). Además, la definición de un método con un parámetro
self
fuera del cuerpo de la clase parece extraña. Así que volvamos a la inicialización dinámica de una clase sin atributos:
MyClass = type(“MyClass”, (object, ), dict())
Después de inicializar una clase vacía, puede agregar métodos dinámicamente, es decir, sin una definición estática explícita:
code = compile('def foo(self): print(“bar”)', "<string>", "exec")
compile
Es una función incorporada que compila el código fuente en un objeto. El código puede ser ejecutado por funciones exec()
o eval()
.
Compilar parámetros de función
- fuente
Código fuente, puede ser un enlace a un módulo. - nombre de archivo
El nombre del archivo en el que se compilará el objeto. - modo
Si se especifica"exec"
, la función compilará el código fuente en un módulo.
El resultado del trabajo
compile
es un objeto de clase code
:
type(code)
<class 'code'>
El objeto
code
debe convertirse en un método. Dado que un método es una función, comenzamos por convertir un code
objeto de clase en un objeto de clase function
. Para hacer esto, importe el módulo types
:
from types import FunctionType, MethodType
Lo importaré
MethodType
porque lo necesitaré más adelante para convertir la función en un método de clase.
function = FunctionType(code.co_consts[0], globals(), “foo”)
Parámetros del método de inicialización de FunctionType
code
Objeto de clasecode
.code.co_consts[0]
Es una llamada a un descriptor deco_consts
clasecode
, que es una tupla con constantes en el código del objeto. Imagine un objetocode
como un módulo con una única función que estamos tratando de agregar como método de clase.0
Es su índice, ya que es la única constante del módulo.globals()
Diccionario de variables globales.name
Parámetro opcional que especifica el nombre de la función.
El resultado es una función:
function
<function foo at 0x7fc79cb5ed90>
type(function)
<class 'function'>
A continuación, debe agregar esta función como método de clase
MyClass
:
MyClass.foo = MethodType(function, MyClass)
Una expresión bastante simple que asigna nuestra función a un método de clase
MyClass
.
m = MyClass()
m.foo()
bar
Advertencia
En el 99% de los casos, puede arreglárselas con definiciones de clases estáticas. Sin embargo, el concepto de metaprogramación es bueno para revelar los aspectos internos de Python. Lo más probable es que le resulte difícil encontrar la aplicación de los métodos descritos aquí, aunque en mi práctica había tal caso.
¿Ha trabajado con objetos dinámicos? ¿Quizás en otros idiomas?