python - otra - ¿Cómo puedo crear dinámicamente clases derivadas de una clase base?
llamar una clase dentro de otra clase python (2)
Por ejemplo, tengo una clase base de la siguiente manera:
class BaseClass(object):
def __init__(self, classtype):
self._type = classtype
De esta clase derivo varias otras clases, por ejemplo
class TestClass(BaseClass):
def __init__(self):
super(TestClass, self).__init__(''Test'')
class SpecialClass(BaseClass):
def __init__(self):
super(TestClass, self).__init__(''Special'')
¿Existe una forma agradable y pitónica de crear esas clases dinámicamente mediante una llamada a función que pone la nueva clase en mi ámbito actual, como:
foo(BaseClass, "My")
a = MyClass()
...
Como habrá comentarios y preguntas sobre por qué necesito esto: todas las clases derivadas tienen exactamente la misma estructura interna con la diferencia de que el constructor toma una cantidad de argumentos previamente indefinidos. Entonces, por ejemplo, MyClass
toma las palabras clave mientras el constructor de la clase TestClass
toma c
.
inst1 = MyClass(a=4)
inst2 = MyClass(a=5)
inst3 = TestClass(b=False, c = "test")
Pero NUNCA deben usar el tipo de la clase como argumento de entrada como
inst1 = BaseClass(classtype = "My", a=4)
Lo hice funcionar pero preferiría el otro modo, es decir, objetos de clase creados dinámicamente.
Este pequeño código le permite crear nuevas clases con nombres dinámicos y nombres de parámetros. La verificación de parámetros en __init__
simplemente no permite parámetros desconocidos, si necesita otras verificaciones, como tipo, o que son obligatorias, simplemente agregue la lógica allí:
class BaseClass(object):
def __init__(self, classtype):
self._type = classtype
def ClassFactory(name, argnames, BaseClass=BaseClass):
def __init__(self, **kwargs):
for key, value in kwargs.items():
# here, the argnames variable is the one passed to the
# ClassFactory call
if key not in argnames:
raise TypeError("Argument %s not valid for %s"
% (key, self.__class__.__name__))
setattr(self, key, value)
BaseClass.__init__(self, name[:-len("Class")])
newclass = type(name, (BaseClass,),{"__init__": __init__})
return newclass
Y esto funciona así, por ejemplo:
>>> SpecialClass = ClassFactory("SpecialClass", "a b c".split())
>>> s = SpecialClass(a=2)
>>> s.a
2
>>> s2 = SpecialClass(d=3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 8, in __init__
TypeError: Argument d not valid for SpecialClass
Veo que está pidiendo insertar los nombres dinámicos en el ámbito de nombres; ahora, eso no se considera una buena práctica en Python; tiene nombres de variables, conocidos en tiempo de codificación, o datos; y los nombres aprendidos en tiempo de ejecución son más ". datos "que" variables "-
Entonces, podrías agregar tus clases a un diccionario y usarlas desde allí:
name = "SpecialClass"
classes = {}
classes[name] = ClassFactory(name, params)
instance = classes[name](...)
Y si su diseño necesita absolutamente los nombres para entrar en el alcance, simplemente haga lo mismo, pero utilice el diccionario devuelto por la llamada globals()
lugar de un diccionario arbitrario:
name = "SpecialClass"
globals()[name] = ClassFactory(name, params)
instance = SpecialClass(...)
(De hecho, sería posible que la función de fábrica de la clase inserte el nombre de forma dinámica en el alcance global de la persona que llama, pero esa es una práctica aún peor, y no es compatible con las implementaciones de Python. La forma de hacerlo sería obtener el llamador marco de ejecución, a través de sys._getframe(1) y estableciendo el nombre de clase en el diccionario global del marco en su atributo f_globals
).
update, tl; dr: Esta respuesta se hizo popular, todavía es muy específica para el cuerpo de la pregunta. La respuesta general sobre cómo "crear dinámicamente clases derivadas de una clase base" en Python es una simple llamada a type
pasando el nuevo nombre de clase, una tupla con la clase base (es) y el cuerpo __dict__
para la nueva clase, como este:
>>> new_class = type("NewClassName", (BaseClass,), {"new_method": lambda self: ...})
actualizar
Cualquier persona que necesite esto también debería verificar el proyecto de dill : afirma ser capaz de extraer y desmantelar las clases como lo hace Pickle en objetos ordinarios, y lo ha vivido en algunas de mis pruebas.
type()
es la función que crea clases (y en particular subclases):
def set_x(self, value):
self.x = value
SubClass = type(''SubClass'', (BaseClass,), {''set_x'': set_x})
# (More methods can be put in SubClass, including __init__().)
obj = SubClass()
obj.set_x(42)
print obj.x # Prints 42
print isinstance(obj, BaseClass) # True