Jestem całkiem nowy w programowaniu obiektowym w języku Python i mam problemy ze zrozumieniem super()
funkcji (nowych klas stylów), szczególnie jeśli chodzi o wielokrotne dziedziczenie.
Na przykład, jeśli masz coś takiego:
class First(object):
def __init__(self):
print "first"
class Second(object):
def __init__(self):
print "second"
class Third(First, Second):
def __init__(self):
super(Third, self).__init__()
print "that's it"
Nie rozumiem: czy Third()
klasa odziedziczy obie metody konstruktora? Jeśli tak, to który z nich będzie uruchamiany za pomocą super () i dlaczego?
A co jeśli chcesz uruchomić drugi? Wiem, że ma to coś wspólnego z kolejnością rozwiązywania metod Python ( MRO ).
python
multiple-inheritance
Callisto
źródło
źródło
super()
można z niego skorzystać. Nie polecałbym używania go z klasami wykorzystującymi dziedziczenie liniowe, gdzie jest to po prostu bezużyteczny narzut.super()
jest to, że zmusza to każdą podklasę do korzystania z niej, podczas gdy gdy nie jest używanasuper()
, każda podklasa może sam zdecydować. Jeśli programista używający go nie wiesuper()
lub nie wie, że został użyty, mogą pojawić się problemy z mro, które są bardzo trudne do wyśledzenia.Odpowiedzi:
Jest to szczegółowo opisane z rozsądną ilością szczegółów przez samego Guido w jego blogu Order Resolution Resolution Order (w tym dwie wcześniejsze próby).
W twoim przykładzie
Third()
zadzwoniFirst.__init__
. Python szuka każdego atrybutu w rodzicach klasy, ponieważ są one wymienione od lewej do prawej. W tym przypadku szukamy__init__
. Jeśli więc zdefiniujeszPython zacznie od patrzenia
First
, a jeśliFirst
nie ma atrybutu, to będzie patrzyłSecond
.Ta sytuacja staje się bardziej złożona, gdy dziedziczenie zaczyna przecinać ścieżki (na przykład, jeśli
First
dziedziczy sięSecond
). Przeczytaj powyższy link, aby uzyskać więcej szczegółów, ale w skrócie, Python spróbuje utrzymać kolejność, w jakiej każda klasa pojawia się na liście dziedziczenia, poczynając od samej klasy potomnej.Na przykład, jeśli miałeś:
MRO byłoby
[Fourth, Second, Third, First].
Nawiasem mówiąc: jeśli Python nie może znaleźć spójnej kolejności rozwiązywania metod, zgłosi wyjątek, zamiast wracać do zachowania, które może zaskoczyć użytkownika.
Edytowano, aby dodać przykład niejednoznacznego MRO:
Powinien
Third
być MRO[First, Second]
lub[Second, First]
? Nie ma oczywistych oczekiwań, a Python zgłosi błąd:Edycja: Widzę kilka osób argumentujących, że w powyższych przykładach brakuje
super()
wywołań, więc pozwól mi wyjaśnić: celem przykładów jest pokazanie, jak zbudowana jest MRO. Są one nie przeznaczone do drukowania „first \ nsecond \ trzecia” lub cokolwiek innego. Możesz - i oczywiście powinieneś bawić się przykładem, dodawaćsuper()
połączenia, sprawdzać, co się dzieje, i lepiej rozumieć model dziedziczenia Pythona. Ale moim celem tutaj jest uproszczenie i pokazanie, jak zbudowano MRO. I jest zbudowany, jak wyjaśniłem:źródło
Twój kod i inne odpowiedzi są błędne. Brakuje
super()
wywołań w pierwszych dwóch klasach, które są wymagane do działania podklas kooperacyjnych.Oto stała wersja kodu:
super()
Wezwanie znajdzie następną metodę w MRO na każdym kroku, dlatego pierwsze i drugie mieć go też inaczej wykonanie zatrzymuje się na końcuSecond.__init__()
.Oto co otrzymuję:
źródło
super
albo nie uruchomi się (z powodu niedopasowania parametrów), albo nie wywoła kilka baz (ponieważ nie napisałeśsuper
w jednej bazie, która zrywa link)!Chciałem nieco wyjaśnić odpowiedź bez życia, ponieważ kiedy zacząłem czytać o tym, jak używać super () w hierarchii wielokrotnego dziedziczenia w Pythonie, nie otrzymałem jej od razu.
Należy zrozumieć, że
super(MyClass, self).__init__()
zapewnia ona następną__init__
metodę zgodnie z zastosowanym algorytmem Order Resolution Ordering (MRO) w kontekście pełnej hierarchii dziedziczenia .Ta ostatnia część jest niezbędna do zrozumienia. Rozważmy jeszcze raz przykład:
Zgodnie z tym artykułem o Guido van Rossum o zamówieniu na metodę , kolejność rozwiązywania
__init__
jest obliczana (przed Python 2.3) przy użyciu „przejścia od pierwszej głębokości od lewej do prawej”:Po usunięciu wszystkich duplikatów, z wyjątkiem ostatniego, otrzymujemy:
Tak więc, obserwujmy, co się dzieje, gdy tworzymy instancję
Third
klasy, npx = Third()
.Third.__init__
wykonuje.Third(): entering
super(Third, self).__init__()
wykonuje się, a MRO zwracaFirst.__init__
wywoływaną funkcję .First.__init__
wykonujeFirst(): entering
super(First, self).__init__()
wykonuje się, a MRO zwracaSecond.__init__
wywoływaną funkcję .Second.__init__
wykonujeSecond(): entering
super(Second, self).__init__()
wykonuje się, a MRO zwracaobject.__init__
wywoływaną funkcję .object.__init__
wykonuje (tam nie ma instrukcji drukowania)Second.__init__
którego następnie drukujeSecond(): exiting
First.__init__
którego następnie drukujeFirst(): exiting
Third.__init__
którego następnie drukujeThird(): exiting
Wyjaśnia to, dlaczego utworzenie trzeciej () powoduje:
Algorytm MRO został ulepszony od Pythona 2.3, aby działał dobrze w skomplikowanych przypadkach, ale sądzę, że użycie „przejścia od głębokości od lewej do prawej” + „usuwanie duplikatów oczekiwanych dla ostatniego” nadal działa w większości przypadków (proszę komentarz, jeśli tak nie jest). Przeczytaj post na blogu autorstwa Guido!
źródło
Third
nie odziedziczyłSecond
, tosuper(First, self).__init__
zadzwoniłby,object.__init__
a po powrocie wydrukowany zostałby „pierwszy”. Ale ponieważThird
dziedziczy po obuFirst
iSecond
zamiast wywoływaniaobject.__init__
poFirst.__init__
MRO dyktuje, żeobject.__init__
zachowywane jest tylko ostatnie wywołanie do , a instrukcje drukowania są odbieraneFirst
iSecond
nie są osiągane aż doobject.__init__
powrotu. PonieważSecond
zadzwonił jako ostatniobject.__init__
, wraca do środkaSecond
przed powrotemFirst
.List[subclass]
jakoList[superclass]
jeślisubclass
jest podklasąsuperclass
(List
pochodzi ztyping
modułu PEP 483 iirc)Nazywa się to Diamentowym Problemem , strona ma wpis w Pythonie, ale w skrócie, Python wywoła metody nadklasy od lewej do prawej.
źródło
object
jest czwartyOto jak rozwiązałem problem posiadania wielokrotnego dziedziczenia z różnymi zmiennymi do inicjalizacji i posiadania wielu MixIns z tym samym wywołaniem funkcji. Musiałem jawnie dodać zmienne do przekazanych ** kwargs i dodać interfejs MixIn, aby był punktem końcowym dla super wywołań.
Oto
A
rozszerzalna klasa podstawowaB
iC
są to klasy MixIn, które zapewniają funkcjęf
.A
iB
zarówno spodziewać parametrv
w swoich__init__
andC
spodziewaw
. Funkcjaf
przyjmuje jeden parametry
.Q
dziedziczy po wszystkich trzech klasach.MixInF
jest interfejsem mixin dlaB
iC
.źródło
args
/kwargs
zamiast nazwanych parametrów.Rozumiem, że to nie odpowiada bezpośrednio na
super()
pytanie, ale uważam, że jest to wystarczająco istotne, aby się nim podzielić.Istnieje również sposób bezpośredniego wywołania każdej odziedziczonej klasy:
Pamiętaj, że jeśli zrobisz to w ten sposób, będziesz musiał zadzwonić do każdego ręcznie, ponieważ jestem pewien
First
, że__init__()
nie zostanie wywołane.źródło
First
iSecond
dziedziczą inną klasę i nazywają ją bezpośrednio, wówczas ta wspólna klasa (punkt początkowy diamentu) jest wywoływana dwukrotnie. super tego unika.object
że ktoś dzwoni dwa razy. Nie myślałem o tym. Chciałem tylko podkreślić, że bezpośrednio wywołujecie klasy nadrzędne.Ogólnie
Zakładając, że wszystko pochodzi od
object
(jesteś sam, jeśli tak nie jest), Python oblicza kolejność rozwiązywania metod (MRO) na podstawie drzewa dziedziczenia klas. MRO spełnia 3 właściwości:Jeśli takie uporządkowanie nie istnieje, błędy w języku Python. Wewnętrznym działaniem tego jest C3 Lineryzacja przodków klas. Przeczytaj o tym tutaj: https://www.python.org/download/releases/2.3/mro/
Zatem w obu poniższych przykładach jest to:
Po wywołaniu metody pierwsze wystąpienie tej metody w MRO jest wywoływane. Każda klasa, która nie implementuje tej metody, jest pomijana. Każde wywołanie w
super
ramach tej metody wywoła kolejne wystąpienie tej metody w MRO. W związku z tym ma znaczenie zarówno to, jak porządkujesz dziedziczenie klas, jak i gdzie wywołujeszsuper
metody.Z
super
najpierw w każdej metodzieChild()
Wyjścia:Z
super
ostatnim w każdej metodzieChild()
Wyjścia:źródło
Left
za pomocąsuper()
zChild
. Załóżmy, że chcę uzyskać dostępRight
od wewnątrzChild
. Czy istnieje sposób, aby uzyskać dostępRight
zChild
wykorzystaniem bardzo? Czy powinienem zadzwonić bezpośrednioRight
z wewnątrzsuper
?O komentarzu @ calfzhou możesz jak zwykle użyć
**kwargs
:Przykład działania online
Wynik:
Możesz także użyć ich pozycjonowanie:
ale musisz pamiętać MRO, to jest naprawdę mylące.
Mogę być trochę irytujący, ale zauważyłem, że ludzie zapomnieli za każdym razem, gdy używają metody,
*args
i**kwargs
kiedy zastępują metodę, podczas gdy jest to jedno z niewielu naprawdę użytecznych i rozsądnych zastosowań tych „magicznych zmiennych”.źródło
Kolejnym jeszcze nieobjętym punktem jest przekazywanie parametrów do inicjalizacji klas. Ponieważ miejsce docelowe
super
zależy od podklasy, jedynym dobrym sposobem na przekazanie parametrów jest ich spakowanie. Następnie uważaj, aby nie mieć tej samej nazwy parametru o różnych znaczeniach.Przykład:
daje:
__init__
Bezpośrednie wywołanie superklasy w celu bardziej bezpośredniego przypisania parametrów jest kuszące, ale kończy się niepowodzeniem, jeśli w superklasie pojawi się jakieśsuper
wywołanie i / lub zmieniony zostanie MRO, a klasa A może zostać wywołana wiele razy, w zależności od implementacji.Podsumowując: dziedziczenie kooperacyjne oraz super i specyficzne parametry inicjalizacji nie działają dobrze.
źródło
Dane wyjściowe to
Call to Third () lokalizuje init zdefiniowany w Third. I wywołanie super w tej procedurze wywołuje init zdefiniowany w First. MRO = [pierwszy, drugi]. Teraz wywołanie super w init zdefiniowane w First będzie kontynuować wyszukiwanie MRO i znajdzie init zdefiniowane w Second, a każde wywołanie super trafi w domyślny obiekt init . Mam nadzieję, że ten przykład wyjaśnia tę koncepcję.
Jeśli nie zadzwonisz super od First. Łańcuch zatrzymuje się, a otrzymasz następujący wynik.
źródło
Ucząc się mitów uczę się czegoś, co nazywa się super (), wbudowaną funkcją, jeśli się nie myli. Wywołanie funkcji super () może pomóc dziedziczeniu przejść przez rodzica i „rodzeństwo” i pomóc ci widzieć jaśniej. Nadal jestem początkującym, ale uwielbiam dzielić się swoimi doświadczeniami z używania tej super () w python2.7.
Jeśli zapoznałeś się z komentarzami na tej stronie, usłyszysz o kolejności rozwiązywania metod (MRO), ponieważ metoda, którą napisałeś, MRO będzie używać schematu Głębokość od pierwszej do lewej do prawej do wyszukiwania i uruchamiania. Możesz zrobić więcej badań na ten temat.
Dodając funkcję super ()
Możesz połączyć wiele instancji i „rodzin” za pomocą super (), dodając każdą i każdą z nich. I wykona metody, przejrzy je i upewni się, że nie przegapiłeś! Jednak dodanie ich przed lub po nie robi różnicy, będziesz wiedział, czy zrobiłeś naukę z ćwiczenia 44. Niech rozpocznie się zabawa !!
Biorąc przykład poniżej, możesz skopiować i wkleić i spróbować go uruchomić:
Jak to działa? Instancja piątej () będzie wyglądać następująco. Każdy krok przechodzi od klasy do klasy, do której dodano funkcję super.
Rodzic został znaleziony i przejdzie do trzeciego i czwartego !!
Teraz wszystkie klasy z super () są dostępne! Klasa nadrzędna została znaleziona i wykonana, a teraz kontynuuje rozpakowywanie funkcji w spadkach, aby dokończyć kody.
Wynik powyższego programu:
Dla mnie dodanie super () pozwala mi lepiej zrozumieć, w jaki sposób Python wykona moje kodowanie i upewni się, że dziedziczenie może uzyskać dostęp do zamierzonej metody.
źródło
Chciałbym dodać do tego, co mówi @Visionscaper na górze:
W tym przypadku interpreter nie odfiltrowuje klasy obiektu, ponieważ jest zduplikowany, a raczej dlatego, że Second pojawia się w pozycji głowy i nie pojawia się w pozycji ogona w podzbiorze hierarchii. Podczas gdy obiekt pojawia się tylko w pozycji ogona i nie jest uważany za silną pozycję w algorytmie C3 w celu ustalenia priorytetu.
Linearyzacja (mro) klasy C, L (C) jest
Scalanie zlinearyzowane odbywa się poprzez wybranie wspólnych klas, które pojawiają się jako nagłówek list, a nie ogon, ponieważ kolejność ma znaczenie (wyjaśnione poniżej)
Linearyzację trzeciego można obliczyć w następujący sposób:
Zatem dla implementacji super () w następującym kodzie:
staje się oczywiste, jak ta metoda zostanie rozwiązana
źródło
W python 3.5+ dziedziczenie wygląda dla mnie przewidywalnie i bardzo miło. Proszę spojrzeć na ten kod:
Wyjścia:
Jak widać, wywołuje foo dokładnie JEDEN czas dla każdego odziedziczonego łańcucha w tej samej kolejności, w jakiej został odziedziczony. Możesz uzyskać to zamówienie, dzwoniąc . mro :
Czwarty -> Trzeci -> Pierwszy -> Drugi -> Baza -> obiekt
źródło
Może jest jeszcze coś, co można dodać, mały przykład z Django rest_framework i dekoratorów. To daje odpowiedź na ukryte pytanie: „dlaczego mimo to miałbym tego chcieć?”
Jak powiedziano: korzystamy z Django rest_framework i używamy widoków ogólnych, a dla każdego rodzaju obiektów w naszej bazie danych znajdujemy się z jedną klasą widoków zapewniającą GET i POST dla list obiektów oraz drugą klasą widokową zapewniającą GET , PUT i DELETE dla poszczególnych obiektów.
Teraz POST, PUT i DELETE chcemy ozdobić za pomocą Django login_required. Zauważ, jak to wpływa na obie klasy, ale nie na wszystkie metody w każdej z klas.
Rozwiązanie może przechodzić wielokrotne dziedziczenie.
Podobnie w przypadku innych metod.
Na liście spadkowej moich konkretnych zajęć dodawałbym moje
LoginToPost
przedListCreateAPIView
iLoginToPutOrDelete
przedRetrieveUpdateDestroyAPIView
. Moje konkretne zajęciaget
pozostałyby bez dekoracji.źródło
Opublikowanie tej odpowiedzi do mojego przyszłego odniesienia.
Python Multiple Inheritance powinien używać modelu diamentowego, a sygnatura funkcji nie powinna się zmieniać w modelu.
Przykładowy fragment kodu to:
Oto klasa A.
object
źródło
A
powinien również dzwonić__init__
.A
nie „wymyślił” tej metody__init__
, więc nie można założyć, że niektóre inne klasy mogły miećA
wcześniej w swoim MRO. Jedyną klasą, której__init__
metoda nie wywołuje (i nie powinna) wywoływać,super().__init__
jestobject
.object
Może myślę, że powinienemclass A (object) :
zamiast tego napisaćA
nie może być,object
jeśli dodajesz parametr do jego__init__
.