Definición de clase dinámica en Python

La definición de objeto dinámico puede entenderse como una definición en tiempo de ejecución. A diferencia de una definición estática, que se usa en la definición de palabra clave familiar de una clase 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 ase 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 typeporque lo hereda de la clase base object:



A.__bases__
(<class 'object'>,)


Tipo de clase object:



type(object)
<class 'type'>


La clase es objectheredada 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 objectes del tipo clase type. El punto es que typees 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 typesirve 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 typeinicializa 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 selffuera 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")


compileEs 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 compilees un objeto de clase code:



type(code)
<class 'code'>


El objeto codedebe convertirse en un método. Dado que un método es una función, comenzamos por convertir un codeobjeto de clase en un objeto de clase function. Para hacer esto, importe el módulo types:



from types import FunctionType, MethodType


Lo importaré MethodTypeporque 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 clase code. code.co_consts[0]Es una llamada a un descriptor de co_constsclase code, que es una tupla con constantes en el código del objeto. Imagine un objeto codecomo un módulo con una única función que estamos tratando de agregar como método de clase. 0Es 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?



Enlaces






All Articles