三重继承导致元类冲突......有时候

时间:2011-07-02 13:48:00

标签: python oop qt4 multiple-inheritance metaclass

看起来我偶然发现了一个地狱级别的地狱,即使我不想和它有任何关系。

我正在使用PySide在Qt4中编写应用程序。我想将事件驱动的部分与从Qt Designer文件生成的UI定义分开。因此,我创建了一个“控制器”类,但为了缓解我的生活,无论如何我多次继承它们。一个例子:

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)

这可以按预期工作。它还具有(QDialogUi_DialogBaseController)的继承。但是当我继承BaseController并尝试继承所述子类(代替BaseController)时,我收到一个错误:

  

TypeError:调用元类库时出错       元类冲突:派生类的元类必须是其所有基类的元类的(非严格)子类

澄清:QMainWindowQDialog都继承自QObject。由于Qt事件系统的特殊性,BaseController也必须继承它。 Ui_类只继承自简单的Python对象类。我搜索了解决方案,但所有这些都涉及故意使用元类的情况。所以我必须做一些非常错误的事情。

编辑:添加图表可能会更清楚我的描述。

工作示例:

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

另一个工作示例:

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

不工作的例子:

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

2 个答案:

答案 0 :(得分:34)

错误消息表明您的层次结构中某处有两个冲突的元类。您需要检查每个类和QT类,以确定冲突的位置。

以下是一些设置相同情况的简单示例代码:

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

我们不能直接对这两个类进行子类化,因为python不知道要使用哪个元类:

>>> 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

错误试图告诉我们的是,我们需要通过引入第三个元类来解决两个元类之间的冲突,第三个元类是基类中所有元类的子类。

我不确定这比错误信息本身更清楚,但基本上,你通过这样做来解决它:

class MetaAB(MetaA, MetaB):
    pass

class Fixed(A, B):
    __metaclass__ = MetaAB

此代码现在可以正确编译和运行。当然,在实际情况中,解决冲突的元类必须决定采用哪种父元类行为,您必须根据应用程序的要求自行计算。

请记住,您继承的类只能获取两个元类。__init__方法中的一个,这些方法有时会完成所有工作,因此在很多情况下,您将会必须添加__init__,以某种方式调用两者,以帮助他们相处。

答案 1 :(得分:8)

我们使用这样的东西:

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)

假设良好的,现代的元类实现卫生(其中元类子类type,以及__init__中可以完成的任何操作),这允许许多元类相处。

真正和必然地在__new__中完成大部分工作的元类,无论如何都很难结合起来。你可以通过确保它的类是多重继承中的第一个元素来偷偷摸摸其中一个。

要使用此功能,您只需声明:

__metaclass__ = CooperativeMeta

对于那些不同元类聚集在一起的类。

在这种情况下,例如:

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

对于不同的MetaA和MetaB而言,这种方法的可能性要高出很多倍,而不仅仅是继承它们来关闭编译器。

希望评论解释代码。只有一个棘手的方面,它是关于删除从不同位置继承的任何给定__metaclass__的冗余调用,并允许没有显式元类的类与其他类很好地匹配。如果它看起来过度,你可以省略它,在你的代码中,只需仔细订购基类。

这使解决方案成为三行并且非常清楚。