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.