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

Posted

技术标签:

【中文标题】三重继承导致元类冲突......有时【英文标题】:Triple inheritance causes metaclass conflict... Sometimes 【发布时间】:2011-09-27 07:06:24 【问题描述】:

看起来我偶然发现了一个元类地狱,即使我不想与它有任何关系。

我正在使用 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)

这按预期工作。它还继承自 (QDialog, Ui_Dialog, BaseController)。但是当我继承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----------------/

【问题讨论】:

我不太擅长 Python 元类,但我认为问题可能在于您的 MainWindow 类定义中父类的排序。只是猜测。 最让我困惑的是 MainWindow 工作,同时将 QDialog、Ui_Dialog 和控制器置于相同的顺序:从 QObject 继承的类、从对象继承的类、从 QObject 继承的类 - 出于某种原因失败。 我不了解python,但是在C++/Qt 中,严格禁止从QObject 进行多重继承。我想知道您是否也遇到了同样的问题,而且它恰好在您的某些情况下有效。 如果您在多重继承方面遇到问题,那么作为备用方案,您始终可以使用单继承和 3 个相互引用的不同对象。 伙计,这是我在这里看到的最可怕的问题标题之一! 【参考方案1】:

错误消息表明您在层次结构中的某处有两个冲突的元类。您需要检查每个类和 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__,以某种方式调用两者,帮助他们相处。

【讨论】:

绝对正确的答案,感谢您帮助我理解这一点。我已经了解了一些关于编程方法的知识(即调整对象而不是继承它的类......)但是当我处理需要它们的元类时,拥有这些知识仍然是有用的。再次感谢。 括号里的形容词non-strict是什么意思?什么时候考虑子类严格,什么时候不考虑?【参考方案2】:

我们使用这样的东西:

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,这比仅仅继承它们以关闭编译器的可能性要高很多倍。

希望 cmets 解释代码。只有一条棘手的线,它是关于删除对从不同地方继承的任何给定__metaclass__ 的冗余调用,并允许没有显式元类的类与其他类很好地配合使用。如果它看起来太过分了,你可以省略它,并在你的代码中,仔细订购基类。

这使解决方案三行并且非常清晰。

【讨论】:

以上是关于三重继承导致元类冲突......有时的主要内容,如果未能解决你的问题,请参考以下文章

从 pytorch 数据集返回索引:更改 __getitem__ 的函数导致元类冲突

unittest 模拟和多重继承:TypeError:元类冲突

涉及 Enum 的多重继承元类冲突

元类冲突、多重继承、实例为父

多继承元类冲突

元类与 Django 中的 modelformset_factory 冲突