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----------------/
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ć.
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ć.
__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.
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