python oop qt4 multiple-inheritance metaclass

python - La herencia triple causa conflicto de metaclass... A veces



oop qt4 (2)

Parece que tropecé con un infierno de metaclase incluso cuando no quería tener nada que ver con eso.

Estoy escribiendo una aplicación en Qt4 usando PySide. Quiero separar la parte impulsada por eventos de la definición de UI, que se genera a partir de los archivos de Qt Designer. Por lo tanto, creo una clase de "controlador", pero para facilitar mi vida, he heredado múltiples de todos modos. Un ejemplo:

class BaseController(QObject): def setupEvents(self, parent): self.window = parent class MainController(BaseController): pass class MainWindow(QMainWindow, Ui_MainWindow, MainController): def __init__(self, parent=None): super(MainWindow, self).__init__(parent) self.setupUi(self) self.setupEvents(self)

Esto funciona como se esperaba También tiene herencia de ( QDialog , Ui_Dialog , BaseController ). Pero cuando hago una subclase de BaseController y trato de heredar de dicha subclase (en lugar de BaseController ), recibo un error:

TypeError: Error al invocar el metaclass basado en el conflicto de las metaclases: la metaclase de una clase derivada debe ser una subclase (no estricta) de las metaclases de todas sus bases

Aclaración: QMainWindow y QDialog heredan de QObject . BaseController también debe heredar de él debido a las peculiaridades del sistema de eventos Qt. Las clases Ui_ solo heredan de la clase de objeto simple de Python. Busqué soluciones, pero todas involucran casos de uso intencional de metaclases. Así que debo estar haciendo algo terriblemente mal.

EDITAR: Mi descripción puede ser más clara al agregar gráficos.

Ejemplo de trabajo:

QObject | /___________________ | object | QMainWindow | BaseController | /---Ui_MainWindow | | | MainController MainWindow-----------------/

Otro ejemplo de trabajo:

QObject | /___________________ | object | QDialog | BaseController | /---Ui_OtherWindow | | | | OtherWindow----------------/

Ejemplo que no funciona:

QObject | /___________________ | object | QDialog | BaseController | /---Ui_OtherWindow | | | OtherController OtherWindow----------------/


El mensaje de error indica que tiene dos metaclases en conflicto en algún lugar de su jerarquía. Necesita examinar cada una de sus clases y las clases de QT para descubrir dónde está el conflicto.

Aquí hay un código de ejemplo simple que establece la misma situación:

class MetaA(type): pass class MetaB(type): pass class A: __metaclass__ = MetaA class B: __metaclass__ = MetaB

No podemos subclasificar ambas clases directamente, ya que Python no sabría qué metaclase usar:

>>> class Broken(A, B): pass ... Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: Error when calling the metaclass bases metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases

Lo que el error intenta decirnos es que tenemos que resolver el conflicto entre las dos metaclases introduciendo una tercera metaclase que es una subclase de todas las metaclases de las clases base.

No estoy seguro de que sea más claro que el mensaje de error en sí, pero básicamente, lo solucionas al hacer esto:

class MetaAB(MetaA, MetaB): pass class Fixed(A, B): __metaclass__ = MetaAB

Este código ahora se compila y se ejecuta correctamente. Por supuesto, en la situación real, su metaclase de resolución de conflictos tendría que decidir cuál de los comportamientos de metaclase padre adoptar, lo que tendrá que averiguar por sí mismo de los requisitos de su aplicación.

Tenga en cuenta que su clase heredada solo obtiene una de las dos metaclases. __init__ métodos, que a veces hacen todo el trabajo, por lo que en muchos casos, tendrá que agregar un __init__ que llame a ambos de alguna manera que los ayude a llevarse bien.


Usamos algo como esto:

class CooperativeMeta(type): def __new__(cls, name, bases, members): #collect up the metaclasses metas = [type(base) for base in bases] # prune repeated or conflicting entries metas = [meta for index, meta in enumerate(metas) if not [later for later in metas[index+1:] if issubclass(later, meta)]] # whip up the actual combined meta class derive off all of these meta = type(name, tuple(metas), dict(combined_metas = metas)) # make the actual object return meta(name, bases, members) def __init__(self, name, bases, members): for meta in self.combined_metas: meta.__init__(self, name, bases, members)

Asumiendo buenas prácticas modernas de implementación de metaclases (donde el type subclase de metaclases y todo lo que se puede hacer en __init__ se realiza allí) esto permite que muchas metaclases se __init__ bien.

Las metaclases que realmente y necesariamente hacen la mayor parte de su trabajo en __new__ , van a ser difíciles de combinar de todos modos. Puede colar uno de ellos aquí asegurándose de que su clase sea el primer elemento en la herencia múltiple.

Para usar esto, solo declaras:

__metaclass__ = CooperativeMeta

para aquellas clases donde se juntan diferentes metaclases.

En este caso, por ejemplo:

class A: __metaclass__ = MetaA class B: __metaclass__ = MetaB class Fixed(A, B): __metaclass__ = CooperativeMeta

Es mucho más probable que funcione correctamente en general para MetaA y MetaB diferentes a simplemente heredarlos para cerrar el compilador.

Con suerte, los comentarios explican el código. Solo hay una línea complicada, y se trata de eliminar llamadas redundantes a cualquier __metaclass__ dada heredada de diferentes lugares y permitir que las clases sin metaclases explícitas funcionen bien con las demás. Si parece excederse, puede omitirlo y en su código, simplemente ordene las clases base con cuidado.

Eso hace que la solución sea tres líneas y bastante clara.