Powiedzmy, że mam scenariusz wielokrotnego dziedziczenia:
class A(object):
# code for A here
class B(object):
# code for B here
class C(A, B):
def __init__(self):
# What's the right code to write here to ensure
# A.__init__ and B.__init__ get called?
Są dwa typowe podejścia do pisania C
„s __init__
:
- (w starym stylu)
ParentClass.__init__(self)
- (nowszy styl)
super(DerivedClass, self).__init__()
Jednak w obu przypadkach, jeśli klasy nadrzędne ( A
i B
) nie są zgodne z tą samą konwencją, kod nie będzie działał poprawnie (niektóre mogą zostać pominięte lub wywoływane wielokrotnie).
Więc jaki jest właściwy sposób? Łatwo jest powiedzieć „po prostu być spójne, wykonaj jedną lub drugą stronę”, ale jeśli A
albo B
są z 3rd biblioteki partii, co wtedy? Czy istnieje podejście, które zapewni wywołanie wszystkich konstruktorów klas nadrzędnych (i we właściwej kolejności i tylko raz)?
Edycja: aby zobaczyć, co mam na myśli, jeśli to zrobię:
class A(object):
def __init__(self):
print("Entering A")
super(A, self).__init__()
print("Leaving A")
class B(object):
def __init__(self):
print("Entering B")
super(B, self).__init__()
print("Leaving B")
class C(A, B):
def __init__(self):
print("Entering C")
A.__init__(self)
B.__init__(self)
print("Leaving C")
Wtedy otrzymuję:
Entering C
Entering A
Entering B
Leaving B
Leaving A
Entering B
Leaving B
Leaving C
Zauważ, że B
init jest wywoływany dwukrotnie. Jeśli zrobię:
class A(object):
def __init__(self):
print("Entering A")
print("Leaving A")
class B(object):
def __init__(self):
print("Entering B")
super(B, self).__init__()
print("Leaving B")
class C(A, B):
def __init__(self):
print("Entering C")
super(C, self).__init__()
print("Leaving C")
Wtedy otrzymuję:
Entering C
Entering A
Leaving A
Leaving C
Zauważ, że B
init nigdy nie jest wywoływany. Wygląda więc na to, że dopóki nie znam / nie kontroluję init klas, z których dziedziczę ( A
i B
) nie mogę dokonać bezpiecznego wyboru dla klasy, którą piszę ( C
).
źródło
Odpowiedzi:
Oba sposoby działają dobrze. Stosowane podejście
super()
prowadzi do większej elastyczności podklas.W metodzie bezpośredniego połączenia
C.__init__
można wywołać zarównoA.__init__
iB.__init__
.Podczas używania
super()
klasy muszą być zaprojektowane do wspólnego dziedziczenia wielokrotnego, gdzieC
wywołaniasuper
, które wywołująA
kod, który będzie również wywoływał kodsuper
wywołującyB
kod. Więcej informacji o tym, co można zrobić, można znaleźć pod adresem http://rhettinger.wordpress.com/2011/05/26/super-considered-supersuper
.[Pytanie odpowiedzi w późniejszej edycji]
W artykule, do którego istnieje odwołanie, pokazano, jak poradzić sobie z tą sytuacją, dodając klasę otoki wokół
A
iB
. Opracowany przykład znajduje się w części zatytułowanej „Jak włączyć klasę niechętną współpracy”.Można by chcieć, aby wielokrotne dziedziczenie było łatwiejsze, pozwalając bez wysiłku komponować klasy samochodów i samolotów, aby uzyskać FlyingCar, ale rzeczywistość jest taka, że oddzielnie zaprojektowane komponenty często wymagają adapterów lub opakowań, zanim zostaną połączone tak płynnie, jak byśmy chcieli :-)
Jeszcze jedna myśl: jeśli nie jesteś zadowolony z funkcji komponowania przy użyciu wielokrotnego dziedziczenia, możesz użyć kompozycji, aby mieć pełną kontrolę nad tym, które metody są wywoływane w jakich sytuacjach.
źródło
super().__init__()
podejście. Jeśli sprawdzamA.__init__()
iB.__init__()
bezpośrednio (jeśli A i B dzwoniąsuper
) otrzymuję wielokrotne wywołanie init B.Odpowiedź na Twoje pytanie zależy od jednego bardzo ważnego aspektu: czy Twoje klasy bazowe są przeznaczone do wielokrotnego dziedziczenia?
Istnieją 3 różne scenariusze:
Klasy bazowe są niepowiązanymi, samodzielnymi klasami.
Jeśli swoją bazę zajęcia są odrębnymi podmiotami, które są w stanie funkcjonować niezależnie i nie znają się nawzajem, oni nie przeznaczone do wielokrotnego dziedziczenia. Przykład:
Ważne: Zauważ, że ani
Foo
ani nieBar
dzwonisuper().__init__()
! Dlatego twój kod nie działał poprawnie. Ze względu na sposób, w jaki dziedziczenie diamentów działa w Pythonie, nie należy wywoływać klas, których klasa bazowa jestobject
super().__init__()
. Jak zauważyłeś, spowodowałoby to przerwanie dziedziczenia wielokrotnego, ponieważ w końcu dzwonisz do innej klasy,__init__
a nieobject.__init__()
. ( Uwaga: Unikaniesuper().__init__()
wobject
-subclasses jest moja osobista rekomendacja i bynajmniej uzgodnione konsensusu w społeczności Pythona Niektórzy ludzie wolą używać.super
W każdej klasie, twierdząc, że zawsze można napisać adapter jeśli klasa nie zachowuje się jak oczekujesz.)Oznacza to również, że nigdy nie powinieneś pisać klasy, która dziedziczy po
object
i nie ma__init__
metody. Brak zdefiniowania__init__
metody w ogóle ma taki sam efekt jak wywołaniesuper().__init__()
. Jeśli twoja klasa dziedziczy bezpośrednio zobject
, upewnij się, że dodajesz pusty konstruktor, taki jak:W każdym razie w tej sytuacji będziesz musiał ręcznie wywołać każdego konstruktora nadrzędnego. Można to zrobić na dwa sposoby:
Bez
super
Z
super
Każda z tych dwóch metod ma swoje zalety i wady. Jeśli używasz
super
, Twoja klasa będzie obsługiwać iniekcję zależności . Z drugiej strony łatwiej jest popełniać błędy. Na przykład, jeśli zmienisz kolejnośćFoo
iBar
(lubiszclass FooBar(Bar, Foo)
), musisz zaktualizowaćsuper
wywołania, aby były zgodne. Bezsuper
Ciebie nie musisz się tym martwić, a kod jest dużo bardziej czytelny.Jedną z klas jest mixin.
Wstawek jest klasa, która jest przeznaczona do stosowania z wielokrotnego dziedziczenia. Oznacza to, że nie musimy ręcznie wywoływać obu konstruktorów nadrzędnych, ponieważ mixin automatycznie wywoła za nas drugi konstruktor. Ponieważ tym razem musimy wywołać tylko jeden konstruktor, możemy to zrobić,
super
aby uniknąć konieczności kodowania na stałe nazwy klasy nadrzędnej.Przykład:
Oto ważne szczegóły:
super().__init__()
i przepuszcza wszystkie otrzymane argumenty.class FooBar(FooMixin, Bar)
. Jeśli kolejność klas bazowych jest nieprawidłowa, konstruktor miksera nigdy nie zostanie wywołany.Wszystkie klasy bazowe są przeznaczone do dziedziczenia kooperatywnego.
Klasy przeznaczone do dziedziczenia kooperacyjnego są bardzo podobne do miksów: przechodzą przez wszystkie nieużywane argumenty do następnej klasy. Tak jak poprzednio, musimy tylko zadzwonić
super().__init__()
a wszystkie konstruktory nadrzędne będą wywoływane łańcuchowo.Przykład:
W tym przypadku kolejność klas nadrzędnych nie ma znaczenia. Równie dobrze moglibyśmy dziedziczyć po
CoopBar
pierwszym, a kod nadal działałby tak samo. Ale to prawda tylko dlatego, że wszystkie argumenty są przekazywane jako argumenty słów kluczowych. Użycie argumentów pozycyjnych ułatwiłoby uzyskanie nieprawidłowej kolejności argumentów, więc w zwyczaju klasy współpracujące przyjmują tylko argumenty słów kluczowych.To także wyjątek od reguły, o której wspomniałem wcześniej: Obie
CoopFoo
iCoopBar
dziedzicząobject
, ale nadal dzwoniąsuper().__init__()
. Gdyby tego nie zrobili, nie byłoby spółdzielczego dziedziczenia.Konkluzja: Prawidłowa implementacja zależy od klas, z których dziedziczysz.
Konstruktor jest częścią publicznego interfejsu klasy. Jeśli klasa jest zaprojektowana jako mieszana lub do dziedziczenia kooperacyjnego, należy to udokumentować. Jeśli dokumenty nie wspominają o niczym takim, można bezpiecznie założyć, że klasa nie została zaprojektowana do wspólnego dziedziczenia wielokrotnego.
źródło
super().__init__(*args, **kwargs)
w miksie i napisaniu go najpierw. To ma taki sens.Każde podejście („nowy styl” lub „stary styl”) zadziała, jeśli masz kontrolę nad kodem źródłowym
A
iB
. W przeciwnym razie może być konieczne użycie klasy adaptera.Dostępny kod źródłowy: prawidłowe użycie „nowego stylu”
Tutaj kolejność rozwiązywania metod (MRO) dyktuje, co następuje:
C(A, B)
A
najpierw dyktujeB
. MRO jestC -> A -> B -> object
.super(A, self).__init__()
dalej wzdłuż łańcucha MRO zainicjowane wC.__init__
celuB.__init__
.super(B, self).__init__()
dalej wzdłuż łańcucha MRO zainicjowane wC.__init__
celuobject.__init__
.Można powiedzieć, że ten przypadek jest przeznaczony do dziedziczenia wielokrotnego .
Dostępny kod źródłowy: prawidłowe użycie „starego stylu”
Tutaj MRO nie ma znaczenia, ponieważ
A.__init__
iB.__init__
są wywoływane wyraźnie.class C(B, A):
działałby równie dobrze.Chociaż ten przypadek nie jest „przeznaczony” do dziedziczenia wielokrotnego w nowym stylu, jak poprzednio, dziedziczenie wielokrotne jest nadal możliwe.
A co, jeśli
A
iB
jesteś z biblioteki innej firmy - tj. Nie masz kontroli nad kodem źródłowymA
iB
? Krótka odpowiedź: należy zaprojektować klasę adaptera, która implementuje niezbędnesuper
wywołania, a następnie użyć pustej klasy do zdefiniowania MRO (zobacz artykuł Raymonda Hettingera nasuper
- szczególnie w sekcji „Jak włączyć klasę niewspółpracującą”).Zewnętrzni rodzice:
A
nie wdrażasuper
;B
robiKlasa
Adapter
implementuje,super
dzięki czemuC
można zdefiniować MRO, który wchodzi w grę, gdysuper(Adapter, self).__init__()
jest wykonywany.A jeśli jest odwrotnie?
Zewnętrzni rodzice:
A
narzędziasuper
;B
nieTutaj ten sam wzorzec, z wyjątkiem tego, że kolejność wykonywania jest przełączana
Adapter.__init__
;super
najpierw wywołanie, potem jawne wywołanie. Zwróć uwagę, że każdy przypadek z nadrzędnymi osobami trzecimi wymaga unikalnej klasy adaptera.Chociaż możesz obsłużyć przypadki, w których nie kontrolujesz kodu źródłowego
A
i nieB
używasz klasy adaptera, prawdą jest, że musisz wiedzieć, jak implementuje się init klas nadrzędnychsuper
(jeśli w ogóle), aby to zrobić.źródło
Jak Raymond powiedział w swojej odpowiedzi, bezpośrednie wywołanie
A.__init__
iB.__init__
działa dobrze, a twój kod byłby czytelny.Jednak nie używa łącza dziedziczenia między
C
tymi klasami. Wykorzystanie tego linku zapewnia większą spójność i sprawia, że ewentualne refaktoryzacje są łatwiejsze i mniej podatne na błędy. Przykład, jak to zrobić:źródło
Ten artykuł pomaga wyjaśnić wspólne dziedziczenie wielokrotne:
http://www.artima.com/weblogs/viewpost.jsp?thread=281127
Wspomina o użytecznej metodzie,
mro()
która pokazuje kolejność rozwiązywania metod. W swojej 2nd przykład, gdy dzwoniszsuper
wA
Thesuper
wezwanie kontynuuje w MRO. OtoB
dlaczego następna klasa w kolejnościB
init jest wywoływany po raz pierwszy.Oto bardziej techniczny artykuł z oficjalnej strony Pythona:
http://www.python.org/download/releases/2.3/mro/
źródło
Jeśli mnożysz klasy podklasy z bibliotek innych firm, to nie, nie ma ślepego podejścia do wywoływania
__init__
metod klasy bazowej (lub jakichkolwiek innych metod), które faktycznie działają niezależnie od tego, jak zostały zaprogramowane klasy bazowe.super
sprawia, że możliwe do klas przeznaczonych do zapisu wspólnie realizować metody jako część kompleksu wielu drzew dziedziczenia, które nie muszą być znane do autora klasowej. Ale nie ma sposobu, aby użyć go do prawidłowego dziedziczenia z dowolnych klas, które mogą, ale nie muszą, używaćsuper
.Zasadniczo to, czy klasa ma być klasyfikowana do podklasy przy użyciu
super
lub z bezpośrednimi wywołaniami do klasy bazowej, jest właściwością, która jest częścią „publicznego interfejsu” klasy i jako taka powinna być udokumentowana. Jeśli korzystasz z bibliotek innych firm w sposób, jakiego oczekiwał autor biblioteki, a biblioteka ma rozsądną dokumentację, zwykle powie ci, co musisz zrobić, aby podklasować określone rzeczy. Jeśli nie, będziesz musiał spojrzeć na kod źródłowy klas, które tworzysz podklasy, i zobaczyć, jaka jest ich konwencja wywoływania klas bazowych. Jeśli łączysz wiele klas z jednej lub więcej bibliotek innych firm w sposób, jakiego nie spodziewali się autorzy biblioteki , konsekwentne wywoływanie metod superklasowych może być niemożliwe superklasowych; jeśli klasa A jest częścią hierarchii używającej,super
a klasa B jest częścią hierarchii, która nie używa super, to żadna opcja nie jest gwarantowana. Musisz wymyślić strategię, która zadziała w każdym konkretnym przypadku.źródło