Тройное наследование вызывает конфликт метакласса... Иногда
Похоже, я наткнулся на метаклассовый ад, даже когда я не хотел иметь с ним ничего общего.
Я пишу приложение в Qt4 с помощью PySide. Я хочу отделить управляемую событиями часть от определения пользовательского интерфейса, которая создается из файлов 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
), я получаю сообщение об ошибке:
TypeError: ошибка при вызове баз метакласса metaclass: метакласс производного класса должен быть (нестрогим) подклассом метаклассов всех его оснований
Уточнение: оба QMainWindow
и QDialog
наследуются от QObject
. BaseController
должен также наследовать от него из-за особенностей системы событий Qt. Классы Ui_ наследуются только от простого класса объектов Python. Я искал решения, но все они включают случаи намеренного использования метаклассов. Поэтому я должен делать что-то ужасное.
EDIT: Мое описание может быть более четким путем добавления графиков.
Рабочий пример:
QObject
| \___________________
| object |
QMainWindow | BaseController
| /---Ui_MainWindow |
| | MainController
MainWindow-----------------/
Другой рабочий пример:
QObject
| \___________________
| object |
QDialog | BaseController
| /---Ui_OtherWindow |
| | |
OtherWindow----------------/
Не работает пример:
QObject
| \___________________
| object |
QDialog | BaseController
| /---Ui_OtherWindow |
| | OtherController
OtherWindow----------------/
Ответы
Ответ 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
Этот код теперь компилируется и выполняется правильно. Конечно, в реальной ситуации ваш мета-класс, разрешающий конфликт, должен будет решить, какое из поведения родительского метакласса принять, что вам нужно будет выяснить из ваших требований к приложениям.
Имейте в виду, что ваш унаследованный класс получает только один из двух методов metaclass. __init__
, которые иногда выполняют всю работу, поэтому во многих случаях вам придется добавить __init__
, который вызывает и в том, и в другом, что помогает им ладить.
Ответ 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)
Предполагая, что хорошая, современная метаклассическая реализация (где выполняется подкласс класса metaclass type
и все, что можно сделать в __init__
), это позволяет многим метаклассам ладить.
Метаклассы, которые действительно и обязательно выполняют большую часть своей работы в __new__
, все равно сложно объединить. Вы можете прокрасть один из них здесь, убедившись, что его класс является первым элементом в множественном наследовании.
Чтобы использовать это, вы просто объявляете:
__metaclass__ = CooperativeMeta
для тех классов, где разные метаклассы объединяются.
В этом случае, например:
class A:
__metaclass__ = MetaA
class B:
__metaclass__ = MetaB
class Fixed(A, B):
__metaclass__ = CooperativeMeta
Это во много раз больше шансов правильно работать по всем разделам для разных MetaA и MetaB, чем просто наследовать их вместе, чтобы закрыть компилятор.
Надеемся, что комментарии объяснят код. Существует только одна сложная строка, и речь идет об удалении избыточных вызовов для любого заданного __metaclass__
, унаследованного из разных мест, и предоставления классам без явного метакласса, чтобы хорошо играть с другими. Если это кажется чрезмерным, вы можете опустить его и в своем коде, просто закажите базовые классы.
Это делает решение трех строк и довольно четким.