三重遗产引发元类冲突……有时候

2024-04-25 06:05:59 发布

您现在位置:Python中文网/ 问答频道 /正文

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

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

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

澄清: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----------------/

Tags: oftheselfuiobjectclassmetaclassparent
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,这比仅仅将它们继承在一起以关闭编译器要正确得多。

希望注释能解释代码。这里只有一个棘手的问题,那就是删除对从不同地方继承的任何给定__metaclass__的多余调用,并允许没有显式元类的类与其他类很好地配合。如果它看起来反应过度,您可以省略它,在您的代码中,只需仔细排序基类。

这使得解决方案三行清晰明了。

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

相关问题 更多 >