Potrójne dziedziczenie powoduje konflikt metaklasyczny ... czasami

Wygląda na to, że natknąłem się na metaklasowe piekło, nawet jeśli nie chciałem mieć z nim nic wspólnego.

Piszę aplikację w Qt4 używając PySide. Chcę oddzielić część sterowaną zdarzeniami od definicji interfejsu użytkownika, która jest generowana z plików projektantów Qt. Stąd tworzę klasy" controller", ale aby ułatwić sobie życie i wiele-dziedziczę je tak czy inaczej. Przykład:

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)

To działa zgodnie z oczekiwaniami. Posiada również dziedziczenie z (QDialog, Ui_Dialog, BaseController). Ale kiedy podklasuję BaseController i staram się dziedziczenie z podklasy (zamiast BaseController), otrzymuję błąd:

TypeError: błąd podczas wywoływania baz metaclass metaklasy klasy pochodnej muszą być podklasą metaklasy wszystkich jej baz

Wyjaśnienie: zarówno QMainWindow jak i QDialog dziedziczą od QObject. BaseController musi również dziedziczyć z niego ze względu na specyfikę QT event system. Klasy Ui_ dziedziczą tylko z prostej klasy obiektu Pythona. Szukałam rozwiązania, ale wszystkie dotyczą przypadków celowego użycia metaklasy. Więc muszę robić coś strasznego nie tak.

EDIT: mój opis może być jaśniejszy przez dodanie Wykresów.

Przykład roboczy:

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

Inny działający przykład:

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

Nie działa przykład:

QObject
|      \___________________
|            object        |
QDialog         |          BaseController
|      /---Ui_OtherWindow  |
|      |                   OtherController
OtherWindow----------------/
Author: Phil, 2011-07-02

2 answers

Komunikat o błędzie wskazuje, że masz dwie sprzeczne metaklasy gdzieś w swojej hierarchii. Musisz zbadać każdą ze swoich klas I klas QT, aby dowiedzieć się, gdzie jest konflikt.

Oto prosty przykładowy kod, który ustawia tę samą sytuację:

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

Nie możemy bezpośrednio podklasować obu tych klas, ponieważ python nie wiedziałby, której metaklasy użyć:

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

Błąd próbuje nam powiedzieć, że musimy rozwiązać konflikt pomiędzy dwoma metaklasami wprowadzając trzecią metaklasę, która jest podklasą wszystkich metaklasów z klas bazowych.

Nie jestem pewien, czy jest to jaśniejsze niż sam komunikat o błędzie, ale zasadniczo naprawiasz go, robiąc to:

class MetaAB(MetaA, MetaB):
    pass

class Fixed(A, B):
    __metaclass__ = MetaAB

Ten kod teraz kompiluje i działa poprawnie. Oczywiście, w rzeczywistej sytuacji, twoja metaklasa rozwiązywania konfliktów musiałaby zdecydować, które z zachowań metaklasy rodzica przyjąć, które będziesz musiał sam wymyślić z twojego wymagania aplikacji.

Pamiętaj, że Twoja odziedziczona Klasa dostaje tylkojedną z dwóch metaklas.__init__ metody, które czasami wykonują całą pracę, więc w wielu przypadkach będziesz musiał dodać __init__, które wywołują oba w jakiś sposób, który pomaga im się dogadać.

 33
Author: tangentstorm,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-08-25 00:44:47

Używamy czegoś takiego:

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)

Zakładając dobrą, nowoczesną higienę implementacji metaklasy (gdzie podklasa metaklasy type i wszystko, co można zrobić w __init__ zostanie tam zrobione), pozwala to wielu metaklasom się dogadać.

Metaklasy, które naprawdę i koniecznie wykonują większość swojej pracy w __new__, i tak będą trudne do połączenia. Możesz przemycić jeden z nich tutaj, upewniając się, że jego klasa jest pierwszym elementem dziedziczenia wielokrotnego.

Aby użyć tego, ty just declare:

__metaclass__ = CooperativeMeta

Dla tych klas, w których spotykają się różne metaklasy.

W tym przypadku, na przykład:

class A:
    __metaclass__ = MetaA
class B:
    __metaclass__ = MetaB
class Fixed(A, B):
    __metaclass__ = CooperativeMeta

Jest wiele razy bardziej prawdopodobne, że będzie to działać poprawnie dla różnych MetaA i MetaB,niż tylko dziedziczenie ich razem, aby zamknąć kompilator.

Miejmy nadzieję, że komentarze wyjaśniają kod. Jest tylko jedna trudna linia, a chodzi o usunięcie zbędnych wywołań do dowolnego __metaclass__ odziedziczonego z różnych miejsc i umożliwienie klasy bez wyraźnej metaklasy, aby ładnie bawić się z innymi. Jeśli wydaje się to przesadne, możesz to pominąć i w kodzie po prostu porządkować klasy bazowe ostrożnie.

To sprawia, że rozwiązanie jest trzy linie i całkiem jasne.

 7
Author: Jon Obermark,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-08-27 15:20:16