Подтвердить что ты не робот

Тройное наследование вызывает конфликт метакласса... Иногда

Похоже, я наткнулся на метаклассовый ад, даже когда я не хотел иметь с ним ничего общего.

Я пишу приложение в 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----------------/
4b9b3361

Ответ 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__, унаследованного из разных мест, и предоставления классам без явного метакласса, чтобы хорошо играть с другими. Если это кажется чрезмерным, вы можете опустить его и в своем коде, просто закажите базовые классы.

Это делает решение трех строк и довольно четким.