2012-04-12 10 views
9

Digamos que tengo una clase A, B y C.¿Cómo agrego dinámicamente mixins como clases base sin obtener errores de MRO?

La clase A y B son ambas clases mixin para la clase C.

class A(object): 
    pass 
class B(object): 
    pass 
class C(object, A, B): 
    pass 

esto no funcionará cuando una instancia de la clase C. tendría que quitar object de la clase C para hacer que funcione. (De lo contrario, obtendrá problemas de MRO).

TypeError: Error when calling the metaclass bases
Cannot create a consistent method resolution
order (MRO) for bases B, object, A

Sin embargo, mi caso es un poco más complicado. En mi caso, la clase C es un servidor donde A y B serán complementos que se cargan al inicio. Estos residen en su propia carpeta.

También tengo una clase llamada Cfactory. En Cfactory tengo un método __new__ que creará un objeto C. completamente funcional en el método __new__ Yo busco para los plugins, los carga usando __import__, y luego asignarlos a C.__bases__ += (loadedClassTypeGoesHere,)

Así que la siguiente es una posibilidad: (hacía bastante abstracto)

class A(object): 
    def __init__(self): pass 
    def printA(self): print "A" 
class B(object): 
    def __init__(self): pass 
    def printB(self): print "B" 
class C(object): 
    def __init__(self): pass 
class Cfactory(object): 
    def __new__(cls): 
     C.__bases__ += (A,) 
     C.__bases__ += (B,) 
     return C() 

De nuevo, esto no va a funcionar, y dará a los errores de MRO nuevo:

TypeError: Cannot create a consistent method resolution
order (MRO) for bases object, A

Un Una solución fácil para esto es eliminar la clase base object de A y B. Sin embargo esto hará que los objetos de estilo antiguo que deben ser evitados cuando estos plugins se ejecutan autónomo (que debería ser posible, unittest sabia)

Otra solución fácil es la eliminación de object de C pero esto también hará que sea una clase de estilo antiguo y C.__bases__ no estarán disponibles por lo que no puedo agregar objetos adicionales a la base de C

¿Cuál sería una buena solución arquitectónica para esto y cómo harías algo como esto? Por ahora puedo vivir con clases antiguas para los plugins. Pero prefiero no usarlos.

+2

Las clases de estilo antiguo no existen en Python 3, por lo que necesitará una solución eventualmente. –

+0

Realmente cierto. –

Respuesta

10

Piénselo de esta manera: desea que los mixins anulen algunos de los comportamientos de object, por lo que deben estar antes de object en el orden de resolución del método.

Así que hay que cambiar el orden de las bases:

class C(A, B, object): 
    pass 

Debido a this bug, necesita C no heredar de objeto directamente a ser capaz de asignar correctamente a __bases__, y la fábrica en realidad sólo podía ser una función:

class FakeBase(object): 
    pass 

class C(FakeBase): 
    pass 

def c_factory(): 
    for base in (A, B): 
     if base not in C.__bases__: 
      C.__bases__ = (base,) + C.__bases__ 
    return C() 
+1

Sí, probé tu primer ejemplo que realmente daba ese error (lo encontré también con tu código de edición anterior). Déjame trabajar algunas cosas y ver si funciona. –

+0

@DaanTimmer Sí, es desafortunado que el error confunda la cuestión del orden correcto de las bases. – agf

3

no sé los detalles, por lo que tal vez estoy completamente fuera de base aquí, pero parece que está utilizando los mecanismos equivocadas para lograr su diseño.

En primer lugar, ¿por qué es Cfactory una clase, y por qué su método __new__ devuelve una instancia de algo más? Parece una forma extraña de implementar lo que es, naturalmente, una función. Cfactory como lo ha descrito (y se muestra un ejemplo simplificado) no se comporta en absoluto como una clase; no tiene varias instancias que compartan la funcionalidad (de hecho, parece que ha hecho imposible construir instancias de forma natural).

Para ser honesto, C tampoco me parece una clase. Parece que no puedes crear más de una instancia de la misma, de lo contrario terminarías con una lista de bases en constante crecimiento. Eso hace que C sea básicamente un módulo en lugar de una clase, solo que con un texto adicional. Intento evitar el patrón "clase de instancia única para representar la aplicación o algún sistema externo" (aunque sé que es popular porque Java requiere que lo use). Pero el mecanismo de herencia de clase a menudo puede ser útil para cosas que no son realmente clases, como su sistema de complementos.

que hubiera hecho esto con un classmethod en C de encontrar y plugins de carga, invocadas por el módulo que defina C para que sea siempre en un buen estado. Alternativamente, podría usar una metaclase para agregar automáticamente los complementos que encuentre a las bases de clases. La combinación del mecanismo para configurar clase con el mecanismo para crear una instancia de la clase parece incorrecta; es lo opuesto al diseño desacoplado flexible.

Si se crea los plugins no puede ser cargados en el momento C, entonces yo iría con invocando manualmente el classmethod configurador en el punto cuando se puede buscar plugins, antes de crear la instancia C.

En realidad, si la clase no se puede poner en un estado constante en cuanto se crea, probablemente preferiría crear clases dinámicas que modificar las bases de una clase existente. Entonces, el sistema no se bloquea en la clase que se configura una vez y se crea una instancia una vez; Al menos está abierto a la posibilidad de tener múltiples instancias con diferentes conjuntos de complementos cargados. Algo como esto:

def Cfactory(*args, **kwargs): 
    plugins = find_plugins() 
    bases = (C,) + plugins 
    cls = type('C_with_plugins', bases, {}) 
    return cls(*args, **kwargs) 

De esta manera, usted tiene su sola llamada para crear la instancia con C le da una instancia configurada correctamente, pero no tiene efectos secundarios extraños en cualquier otra instancia de hipotéticas que podrían C ya existe, y su comportamiento no depende de si se ha ejecutado antes. Sé que probablemente no necesites ninguna de esas dos propiedades, pero apenas hay más código del que tienes en tu ejemplo simplificado, y ¿por qué romper el modelo conceptual de qué clases son si no tienes que hacerlo?

+0

su consejo con respecto a que Cfactory es una clase y no una función también fue otorgado por agf. Y lo cambié en mi propio código. Habrá algunos métodos genéricos dentro de C que siempre deberían estar disponibles, incluso cuando no hay complementos. Voy a +1 a su respuesta porque es una buena respuesta, pero la respuesta de agf brindó una solución a mi problema actual. Aún así gracias. –

+1

Me gusta esta respuesta, excepto la función Cfactory: crea una clase, para la cual las instancias pueden tener diferentes clases base según los complementos encontrados. El nombre de clase de cada instancia ya no es significativo. Más bien, Cfactory debería crear un nombre de clase que sea único para la combinación de complementos incluidos como bases, pero también idéntico independientemente del orden en que se encontraron los complementos. – Schollii

3

Hay una solución alternativa: crear una clase auxiliar, con un buen nombre, como PluginBase. Y use eso la herencia de, en lugar de objeto.

Esto hace que el código sea más legible (imho) y da la circunstancia del error.

class PluginBase(object): pass 
class ServerBase(object): pass 

class pluginA(PluginBase): "Now it is clearly a plugin class" 
class pluginB(PluginBase): "Another plugin" 

class Server1(ServerBase, pluginA, pluginB): "This works" 
class Server2(ServerBase): pass 
Server2.__base__ += (PluginA,) # This also works 

Como nota: Probablemente no se necesita la fábrica ; es necesario en C++, pero difícilmente en Python

+0

Estoy usando Python 2.7.6 en OS X y su última línea no funciona. Obtengo "TypeError: tipo (s) de operandos no soportados para + =: 'tipo' y 'tuple'" (después de corregir el error tipográfico de la P mayúscula). – mikeazo

Cuestiones relacionadas