三重继承导致元类冲突... 有时
看起来我不想碰的元类问题却意外找上我了。
我正在用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)
这个代码按预期工作。它同时继承了(QDialog
、Ui_Dialog
、BaseController
)。但是当我从BaseController
子类化,并尝试从这个子类继承(替换掉BaseController
)时,我收到了一个错误:
类型错误:调用元类基类时出错 元类冲突:派生类的元类必须是其所有基类元类的(非严格)子类
澄清一下:QMainWindow
和QDialog
都继承自QObject
。BaseController
也必须继承自它,因为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 个回答
我们用的东西大概是这样的:
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__
方法,以某种方式调用这两个方法,帮助它们和谐相处。