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

43 投票
2 回答
17463 浏览
提问于 2025-04-16 20:42

看起来我不想碰的元类问题却意外找上我了。

我正在用PySide写一个Qt4的应用程序。我想把事件驱动的部分和从Qt Designer文件生成的用户界面分开。因此,我创建了一个“控制器”类,但为了方便起见,我还是用了多重继承。举个例子:

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)时,我收到了一个错误:

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

澄清一下:QMainWindowQDialog都继承自QObjectBaseController也必须继承自它,因为Qt事件系统的特殊性。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 个回答

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__ 的冗余调用,并让没有明确元类的类能够和其他类友好相处。如果觉得这部分太复杂,可以不写,直接在你的代码里仔细安排基类的顺序就行。

这样解决方案就变成了三行代码,而且相当清晰。

39

这个错误信息告诉我们,你的类层次结构中有两个相互冲突的元类。你需要检查一下你的每个类和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__方法,以某种方式调用这两个方法,帮助它们和谐相处。

撰写回答