Тройное наследование вызывает конфликт метаклассов ... Иногда - PullRequest
39 голосов
/ 02 июля 2011

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

Я пишу приложение в 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: Ошибка при вызове конфликта метакласса баз метаклассов: метакласспроизводного класса должен быть (нестрогим) подклассом метаклассов всех его баз

Пояснение: и 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 ]

34 голосов
/ 06 сентября 2011

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

8 голосов
/ 27 августа 2012

Мы используем что-то вроде этого:

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

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

...