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

Red*_*Red 39 python oop metaclass qt4 multiple-inheritance

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

我正在使用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)
Run Code Online (Sandbox Code Playgroud)

这按预期工作.它也有继承自(QDialog,Ui_Dialog,BaseController).但是当我BaseController继承并尝试从所述子类继承(代替BaseController)时,我收到一个错误:

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

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

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

工作范例:

QObject
|      \___________________
|            object        |
QMainWindow     |          BaseController
|      /---Ui_MainWindow   |
|      |                   MainController
MainWindow-----------------/
Run Code Online (Sandbox Code Playgroud)

另一个工作示例:

QObject
|      \___________________
|            object        |
QDialog         |          BaseController
|      /---Ui_OtherWindow  |
|      |                   |
OtherWindow----------------/
Run Code Online (Sandbox Code Playgroud)

不工作的例子:

QObject
|      \___________________
|            object        |
QDialog         |          BaseController
|      /---Ui_OtherWindow  |
|      |                   OtherController
OtherWindow----------------/
Run Code Online (Sandbox Code Playgroud)

tan*_*orm 34

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

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

class MetaA(type):
    pass
class MetaB(type):
    pass
class A:
    __metaclass__ = MetaA
class B:
    __metaclass__ = MetaB
Run Code Online (Sandbox Code Playgroud)

我们不能直接对这两个类进行子类化,因为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
Run Code Online (Sandbox Code Playgroud)

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

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

class MetaAB(MetaA, MetaB):
    pass

class Fixed(A, B):
    __metaclass__ = MetaAB
Run Code Online (Sandbox Code Playgroud)

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

记住,你的继承类只得到一个两元类的.__init__方法,有时会完成所有工作,所以在很多情况下,你将不得不以__init__某种方式添加一个调用,以帮助他们相处.


Jon*_*ark 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)
Run Code Online (Sandbox Code Playgroud)

假设良好的,现代的元类实现卫生(其中元类子类type,以及可以在__init__那里完成的任何事情),这允许许多元类相处.

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

要使用它,您只需声明:

__metaclass__ = CooperativeMeta
Run Code Online (Sandbox Code Playgroud)

对于那些不同元类聚集在一起的课程.

在这种情况下,例如:

class A:
    __metaclass__ = MetaA
class B:
    __metaclass__ = MetaB
class Fixed(A, B):
    __metaclass__ = CooperativeMeta
Run Code Online (Sandbox Code Playgroud)

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

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

这使得解决方案有三条线并且相当清晰.