Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
401 views
in Technique[技术] by (71.8m points)

python - Triple inheritance causes metaclass conflict... Sometimes

Looks like I stumbled upon a metaclass hell even when I didn't wanted anything to do with it.

I'm writing an app in Qt4 using PySide. I want to separate event-driven part from UI definition, which is generated from Qt Designer files. Hence I create a "controller" classes, but to ease my life I multiple-inherit them anyways. An example:

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)

This works as expected. It also has inheritance from (QDialog, Ui_Dialog, BaseController). But when I subclass BaseController and try to inherit from said subclass (in place of BaseController), I receive an error:

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

Clarification: Both QMainWindow and QDialog inherit from QObject. BaseController must also inherit from it because of Qt event system peculiarities. Ui_ classes only inherit from simple Python object class. I searched for solutions, but all of them involve cases of intentionally using metaclasses. So I must be doing something terribly wrong.

EDIT: My description may be clearer by adding graphs.

Working example:

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

Another working example:

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

Not working example:

QObject
|      \___________________
|            object        |
QDialog         |          BaseController
|      /---Ui_OtherWindow  |
|      |                   OtherController
OtherWindow----------------/
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

The error message indicates that you have two conflicting metaclasses somewhere in your hierarchy. You need to examine each of your classes and the QT classes to figure out where the conflict is.

Here's some simple example code that sets up the same situation:

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

We can't subclass both of those classes directly, because python wouldn't know which metaclass to use:

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

What the error is trying to tell us is that we need to resolve the conflict between the two metaclasses by introducing a third metaclass that is a subclass of all the metaclasses from the base classes.

I'm not sure that's any clearer than the error message itself, but basically, you fix it by doing this:

class MetaAB(MetaA, MetaB):
    pass

class Fixed(A, B):
    __metaclass__ = MetaAB

This code now compiles and runs correctly. Of course, in the real situation, your conflict-resolving metaclass would have to decide which of the parent metaclass behaviors to adopt, which you'll have to figure out for yourself from your application's requirements.

Bear in mind that your inherited class only gets one of the two metaclass.__init__ methods, which sometimes do all the work, so in a lot of cases, you are going to have to add an __init__ that calls into both in some way that helps them get along.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...