Jak działa super () Pythona w przypadku wielokrotnego dziedziczenia?

887

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

Callisto
źródło
W rzeczywistości wielokrotne dziedziczenie jest jedynym przypadkiem, w którym 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.
Bachsau,
9
@Bachsau jest technicznie poprawny, ponieważ jest niewielkim narzutem, ale super () jest bardziej pythonowy i pozwala na przefakturowanie i zmiany kodu w czasie. Użyj super (), chyba że naprawdę potrzebujesz nazwanej metody specyficznej dla klasy.
Paul Whipp,
2
Innym problemem super()jest to, że zmusza to każdą podklasę do korzystania z niej, podczas gdy gdy nie jest używana super(), każda podklasa może sam zdecydować. Jeśli programista używający go nie wie super()lub nie wie, że został użyty, mogą pojawić się problemy z mro, które są bardzo trudne do wyśledzenia.
Bachsau,
Uważam, że praktycznie każda odpowiedź tutaj jest myląca w taki czy inny sposób. Zamiast tego poleciłbyś tutaj .
matanster

Odpowiedzi:

709

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()zadzwoni First.__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 zdefiniujesz

class Third(First, Second):
    ...

Python zacznie od patrzenia First, a jeśli Firstnie 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 Firstdziedziczy 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ś:

class First(object):
    def __init__(self):
        print "first"

class Second(First):
    def __init__(self):
        print "second"

class Third(First):
    def __init__(self):
        print "third"

class Fourth(Second, Third):
    def __init__(self):
        super(Fourth, self).__init__()
        print "that's it"

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:

class First(object):
    def __init__(self):
        print "first"

class Second(First):
    def __init__(self):
        print "second"

class Third(First, Second):
    def __init__(self):
        print "third"

Powinien Thirdbyć MRO [First, Second]lub [Second, First]? Nie ma oczywistych oczekiwań, a Python zgłosi błąd:

TypeError: Error when calling the metaclass bases
    Cannot create a consistent method resolution order (MRO) for bases Second, First

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:

>>> Fourth.__mro__
(<class '__main__.Fourth'>,
 <class '__main__.Second'>, <class '__main__.Third'>,
 <class '__main__.First'>,
 <type 'object'>)
rbp
źródło
12
Staje się bardziej interesujący (i prawdopodobnie bardziej zagmatwany), gdy zaczynasz wywoływać super () w Pierwszym, Drugim i Trzecim [ pastebin.com/ezTyZ5Wa ].
gatoatigrado
52
Myślę, że brak super połączeń w pierwszych klasach jest naprawdę dużym problemem z tą odpowiedzią; bez dyskusji jak / dlaczego utracono tak ważne krytyczne zrozumienie pytania.
Sam Hartman,
3
Ta odpowiedź jest po prostu błędna. Bez wywołań super () w rodzicach nic się nie stanie. Odpowiedź @ lifeless jest poprawna.
Cerin,
8
@Cerin Celem tego przykładu jest pokazanie, jak zbudowana jest MRO. Ten przykład NIE jest przeznaczony do wypisania „first \ nsecond \ Third” lub cokolwiek innego. I MRO jest rzeczywiście poprawny: czwarty .__ mro__ == (<klasa „ główna. Czwarta”>, <klasa „ główna. Drugie”>, <klasa „ główna. Trzecia”>, <klasa „ główna. Pierwsza”>, < wpisz „object”>)
rbp
2
O ile widzę, w tej odpowiedzi brakuje jednego z pytań OP, czyli „A co, jeśli chcesz uruchomić drugie?”. Chciałbym zobaczyć odpowiedź na to pytanie. Czy mamy po prostu jawnie nazwać klasę podstawową?
Ray
251

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:

class First(object):
    def __init__(self):
        super(First, self).__init__()
        print("first")

class Second(object):
    def __init__(self):
        super(Second, self).__init__()
        print("second")

class Third(First, Second):
    def __init__(self):
        super(Third, self).__init__()
        print("third")

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ńcu Second.__init__().

Oto co otrzymuję:

>>> Third()
second
first
third
bez życia
źródło
90
Co zrobić, jeśli te klasy potrzebują różnych parametrów, aby się zainicjować?
Calfzhou
2
„podklasowanie kooperatywne”
Quant Metropolis
6
W ten sposób zostaną wykonane metody inicjujące BOTH klas podstawowych, podczas gdy oryginalny przykład wywołuje tylko pierwszą inicjację napotkaną w MRO. Myślę, że implikuje to termin „podklasowanie kooperatywne”, ale przydatne byłoby wyjaśnienie (wiesz, że „wyraźne jest lepsze niż niejawne”))
Quant Metropolis,
1
Tak, jeśli przekazujesz różne parametry do metody wywoływanej przez super, wszystkie implementacje tej metody idące w górę MRO w kierunku object () muszą mieć kompatybilne podpisy. Można to osiągnąć za pomocą parametrów słowa kluczowego: zaakceptuj więcej parametrów niż używa metoda i zignoruj ​​dodatkowe. Zasadniczo uważa się to za brzydkie i w większości przypadków dodawanie nowych metod jest lepsze, ale init jest (prawie?) Unikalny jako specjalna nazwa metody, ale z parametrami zdefiniowanymi przez użytkownika.
bez życia
15
Projekt wielokrotnego dziedziczenia jest bardzo zły w Pythonie. Klasy podstawowe prawie muszą wiedzieć, kto je wyprowadzi i ile innych pochodnych klas wyprowadzi, i w jakiej kolejności ... w przeciwnym razie superalbo nie uruchomi się (z powodu niedopasowania parametrów), albo nie wywoła kilka baz (ponieważ nie napisałeś superw jednej bazie, która zrywa link)!
Nawaz
186

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:

#!/usr/bin/env python2

class First(object):
  def __init__(self):
    print "First(): entering"
    super(First, self).__init__()
    print "First(): exiting"

class Second(object):
  def __init__(self):
    print "Second(): entering"
    super(Second, self).__init__()
    print "Second(): exiting"

class Third(First, Second):
  def __init__(self):
    print "Third(): entering"
    super(Third, self).__init__()
    print "Third(): exiting"

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

Third --> First --> object --> Second --> object

Po usunięciu wszystkich duplikatów, z wyjątkiem ostatniego, otrzymujemy:

Third --> First --> Second --> object

Tak więc, obserwujmy, co się dzieje, gdy tworzymy instancję Thirdklasy, np x = Third().

  1. Według MRO Third.__init__wykonuje.
    • odciski Third(): entering
    • następnie super(Third, self).__init__()wykonuje się, a MRO zwraca First.__init__wywoływaną funkcję .
  2. First.__init__ wykonuje
    • odciski First(): entering
    • następnie super(First, self).__init__()wykonuje się, a MRO zwraca Second.__init__wywoływaną funkcję .
  3. Second.__init__ wykonuje
    • odciski Second(): entering
    • następnie super(Second, self).__init__()wykonuje się, a MRO zwraca object.__init__wywoływaną funkcję .
  4. object.__init__ wykonuje (tam nie ma instrukcji drukowania)
  5. wykonanie wraca do Second.__init__którego następnie drukujeSecond(): exiting
  6. wykonanie wraca do First.__init__którego następnie drukujeFirst(): exiting
  7. wykonanie wraca do Third.__init__którego następnie drukujeThird(): exiting

Wyjaśnia to, dlaczego utworzenie trzeciej () powoduje:

Third(): entering
First(): entering
Second(): entering
Second(): exiting
First(): exiting
Third(): exiting

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!

Visionscaper
źródło
6
Nadal nie rozumiem, dlaczego: Inside init First super (First, self) .__ init __ () wywołuje init Second, ponieważ tak nakazuje MRO!
user389955
@ user389955 Utworzony obiekt jest typu Third, który ma wszystkie metody inicjowania. Jeśli więc zakładasz, że MRO tworzy listę wszystkich funkcji inicjujących w określonej kolejności, przy każdym superwołaniu robisz krok do przodu, aż dojdziesz do końca.
Sreekumar R
15
Myślę, że krok 3 wymaga więcej wyjaśnień: jeśli Thirdnie odziedziczył Second, to super(First, self).__init__zadzwoniłby, object.__init__a po powrocie wydrukowany zostałby „pierwszy”. Ale ponieważ Thirddziedziczy po obu Firsti Secondzamiast wywoływania object.__init__po First.__init__MRO dyktuje, że object.__init__zachowywane jest tylko ostatnie wywołanie do , a instrukcje drukowania są odbierane Firsti Secondnie są osiągane aż do object.__init__powrotu. Ponieważ Secondzadzwonił jako ostatni object.__init__, wraca do środka Secondprzed powrotem First.
MountainDrew
1
Co ciekawe, pycharm zdaje się wiedzieć wszystko to (jej podpowiedzi mówić o jakie parametry przejść z którymi rozmowy Super. Ma też pewne pojęcie kowariancji wejść, więc rozpoznaje List[subclass]jako List[superclass]jeśli subclassjest podklasą superclass( Listpochodzi z typingmodułu PEP 483 iirc)
Reb.Cabin
Fajny post, ale brakuje mi informacji dotyczących argumentów konstruktorów, tj. Co się stanie, jeśli Drugi i Pierwszy oczekują odrębnych argumentów? Konstruktor First będzie musiał przetworzyć niektóre argumenty, a resztę przekazać do Second. Czy to prawda? Nie brzmi dla mnie poprawnie, że First musi wiedzieć o wymaganych argumentach dla Second.
Christian K.
58

Nazywa się to Diamentowym Problemem , strona ma wpis w Pythonie, ale w skrócie, Python wywoła metody nadklasy od lewej do prawej.

monoceres
źródło
To nie jest problem z diamentem. Problem diamentów obejmuje cztery klasy, a pytanie PO dotyczy tylko trzech.
Ian Goodfellow,
147
objectjest czwarty
GP89,
28

Oto 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 Arozszerzalna klasa podstawowa Bi Csą to klasy MixIn, które zapewniają funkcję f. Ai Bzarówno spodziewać parametr vw swoich __init__and Cspodziewa w. Funkcja fprzyjmuje jeden parametr y. Qdziedziczy po wszystkich trzech klasach. MixInFjest interfejsem mixin dla Bi C.


class A(object):
    def __init__(self, v, *args, **kwargs):
        print "A:init:v[{0}]".format(v)
        kwargs['v']=v
        super(A, self).__init__(*args, **kwargs)
        self.v = v


class MixInF(object):
    def __init__(self, *args, **kwargs):
        print "IObject:init"
    def f(self, y):
        print "IObject:y[{0}]".format(y)


class B(MixInF):
    def __init__(self, v, *args, **kwargs):
        print "B:init:v[{0}]".format(v)
        kwargs['v']=v
        super(B, self).__init__(*args, **kwargs)
        self.v = v
    def f(self, y):
        print "B:f:v[{0}]:y[{1}]".format(self.v, y)
        super(B, self).f(y)


class C(MixInF):
    def __init__(self, w, *args, **kwargs):
        print "C:init:w[{0}]".format(w)
        kwargs['w']=w
        super(C, self).__init__(*args, **kwargs)
        self.w = w
    def f(self, y):
        print "C:f:w[{0}]:y[{1}]".format(self.w, y)
        super(C, self).f(y)


class Q(C,B,A):
    def __init__(self, v, w):
        super(Q, self).__init__(v=v, w=w)
    def f(self, y):
        print "Q:f:y[{0}]".format(y)
        super(Q, self).f(y)
brent.payne
źródło
Myślę, że może to być osobne pytanie i odpowiedź, ponieważ MRO jest wystarczająco dużym tematem samo w sobie, bez zajmowania się różnymi argumentami w funkcji z dziedziczeniem (wielokrotne dziedziczenie jest tego szczególnym przypadkiem).
bez życia
8
Teoretycznie tak. Praktycznie ten scenariusz pojawiał się za każdym razem, gdy napotykałem dziedziczenie Diamentów w pythonie, więc dodałem go tutaj. Ponieważ do tego miejsca podążam za każdym razem, gdy nie mogę uniknąć dziedziczenia diamentów. Oto kilka dodatkowych linków dla przyszłego mnie: rhettinger.wordpress.com/2011/05/26/super-consanted-super code.activestate.com/recipes/…
brent.payne 3'14
To, czego chcemy, to programy o semantycznie znaczących nazwach parametrów. Ale w tym przykładzie prawie wszystkie parametry są anonimowo nazwane, co znacznie utrudni oryginalnemu programistowi udokumentowanie kodu i odczytanie kodu przez innego programistę.
Arthur
Docenione zostanie żądanie ściągnięcia do repozytorium github z opisowymi nazwami
brent.payne
@ brent.payne Myślę, że @Arthur oznaczało, że całe twoje podejście opiera się na użyciu args/ kwargszamiast nazwanych parametrów.
maksymalnie
25

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:


class First(object):
    def __init__(self):
        print '1'

class Second(object):
    def __init__(self):
        print '2'

class Third(First, Second):
    def __init__(self):
        Second.__init__(self)

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.

Seaux
źródło
5
Nie zostanie wywołany, ponieważ nie wywołałeś każdej odziedziczonej klasy. Problem polega raczej na tym, że jeśli Firsti Seconddziedziczą inną klasę i nazywają ją bezpośrednio, wówczas ta wspólna klasa (punkt początkowy diamentu) jest wywoływana dwukrotnie. super tego unika.
Trilarion
@Trilarion Tak, byłem pewien, że nie. Jednak nie wiedziałem definitywnie i nie chciałem mówić tak, jakbym wiedział, chociaż było to bardzo mało prawdopodobne. To dobra uwaga na temat tego, 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.
Seaux
Niestety, to się psuje, jeśli init próbuje uzyskać dostęp do jakichkolwiek prywatnych metod :(
Erik Aronesty
21

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:

  • Dzieci z klasy stają przed rodzicami
  • Lewi rodzice mają pierwszeństwo przed prawymi rodzicami
  • Klasa pojawia się tylko raz w MRO

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:

  1. Dziecko
  2. Lewo
  3. Dobrze
  4. Rodzic

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 superramach 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łujesz supermetody.

Z supernajpierw w każdej metodzie

class Parent(object):
    def __init__(self):
        super(Parent, self).__init__()
        print "parent"

class Left(Parent):
    def __init__(self):
        super(Left, self).__init__()
        print "left"

class Right(Parent):
    def __init__(self):
        super(Right, self).__init__()
        print "right"

class Child(Left, Right):
    def __init__(self):
        super(Child, self).__init__()
        print "child"

Child() Wyjścia:

parent
right
left
child

Z superostatnim w każdej metodzie

class Parent(object):
    def __init__(self):
        print "parent"
        super(Parent, self).__init__()

class Left(Parent):
    def __init__(self):
        print "left"
        super(Left, self).__init__()

class Right(Parent):
    def __init__(self):
        print "right"
        super(Right, self).__init__()

class Child(Left, Right):
    def __init__(self):
        print "child"
        super(Child, self).__init__()

Child() Wyjścia:

child
left
right
parent
Zags
źródło
Widzę, że możesz uzyskać dostęp Leftza pomocą super()z Child. Załóżmy, że chcę uzyskać dostęp Rightod wewnątrz Child. Czy istnieje sposób, aby uzyskać dostęp Rightz Childwykorzystaniem bardzo? Czy powinienem zadzwonić bezpośrednio Rightz wewnątrz super?
alpha_989
4
@ alpha_989 Jeśli chcesz uzyskać dostęp tylko do metody określonej klasy, powinieneś odwoływać się bezpośrednio do tej klasy, zamiast używać super. Super polega na podążaniu za łańcuchem dziedziczenia, a nie na metodzie określonej klasy.
Zags
1
Dzięki za wyraźne wspomnienie „Klasa pojawia się tylko raz w MRO”. To rozwiązało mój problem. Teraz w końcu rozumiem, jak działa wielokrotne dziedziczenie. Ktoś musiał wspomnieć o właściwościach MRO!
Tushar Vazirani,
18

O komentarzu @ calfzhou możesz jak zwykle użyć **kwargs:

Przykład działania online

class A(object):
  def __init__(self, a, *args, **kwargs):
    print("A", a)

class B(A):
  def __init__(self, b, *args, **kwargs):
    super(B, self).__init__(*args, **kwargs)
    print("B", b)

class A1(A):
  def __init__(self, a1, *args, **kwargs):
    super(A1, self).__init__(*args, **kwargs)
    print("A1", a1)

class B1(A1, B):
  def __init__(self, b1, *args, **kwargs):
    super(B1, self).__init__(*args, **kwargs)
    print("B1", b1)


B1(a1=6, b1=5, b="hello", a=None)

Wynik:

A None
B hello
A1 6
B1 5

Możesz także użyć ich pozycjonowanie:

B1(5, 6, b="hello", a=None)

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, *argsi **kwargskiedy zastępują metodę, podczas gdy jest to jedno z niewielu naprawdę użytecznych i rozsądnych zastosowań tych „magicznych zmiennych”.

Marco Sulla
źródło
Wow, to naprawdę brzydkie. Szkoda, że ​​nie można po prostu powiedzieć, do której konkretnej nadklasy chcesz zadzwonić. Mimo to daje mi to jeszcze większą motywację do używania kompozycji i unikania wielokrotnego dziedziczenia, takiego jak zaraza.
Tom Busby
15

Kolejnym jeszcze nieobjętym punktem jest przekazywanie parametrów do inicjalizacji klas. Ponieważ miejsce docelowe superzależ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:

class A(object):
    def __init__(self, **kwargs):
        print('A.__init__')
        super().__init__()

class B(A):
    def __init__(self, **kwargs):
        print('B.__init__ {}'.format(kwargs['x']))
        super().__init__(**kwargs)


class C(A):
    def __init__(self, **kwargs):
        print('C.__init__ with {}, {}'.format(kwargs['a'], kwargs['b']))
        super().__init__(**kwargs)


class D(B, C): # MRO=D, B, C, A
    def __init__(self):
        print('D.__init__')
        super().__init__(a=1, b=2, x=3)

print(D.mro())
D()

daje:

[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
D.__init__
B.__init__ 3
C.__init__ with 1, 2
A.__init__

__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ś superwywoł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.

Trilarion
źródło
5
class First(object):
  def __init__(self, a):
    print "first", a
    super(First, self).__init__(20)

class Second(object):
  def __init__(self, a):
    print "second", a
    super(Second, self).__init__()

class Third(First, Second):
  def __init__(self):
    super(Third, self).__init__(10)
    print "that's it"

t = Third()

Dane wyjściowe to

first 10
second 20
that's it

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.

first 10
that's it
Seraj Ahmad
źródło
1
to dlatego, że w klasie First nazywałeś najpierw „print”, a następnie „super”.
skaliste qi
2
miało to zilustrować kolejność połączeń
Seraj Ahmad
4

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

super(First, self).__init__() #example for class First.

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

class First(object):
    def __init__(self):

        print("first")

class Second(First):
    def __init__(self):
        print("second (before)")
        super(Second, self).__init__()
        print("second (after)")

class Third(First):
    def __init__(self):
        print("third (before)")
        super(Third, self).__init__()
        print("third (after)")


class Fourth(First):
    def __init__(self):
        print("fourth (before)")
        super(Fourth, self).__init__()
        print("fourth (after)")


class Fifth(Second, Third, Fourth):
    def __init__(self):
        print("fifth (before)")
        super(Fifth, self).__init__()
        print("fifth (after)")

Fifth()

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.

1.) print("fifth (before)")
2.) super()>[Second, Third, Fourth] (Left to right)
3.) print("second (before)")
4.) super()> First (First is the Parent which inherit from object)

Rodzic został znaleziony i przejdzie do trzeciego i czwartego !!

5.) print("third (before)")
6.) super()> First (Parent class)
7.) print ("Fourth (before)")
8.) super()> First (Parent class)

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.

9.) print("first") (Parent)
10.) print ("Fourth (after)") (Class Fourth un-box)
11.) print("third (after)") (Class Third un-box)
12.) print("second (after)") (Class Second un-box)
13.) print("fifth (after)") (Class Fifth un-box)
14.) Fifth() executed

Wynik powyższego programu:

fifth (before)
second (before
third (before)
fourth (before)
first
fourth (after)
third (after)
second (after)
fifth (after)

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.

Spotkamy się
źródło
Dzięki za szczegółowe demo!
Tushar Vazirani,
3

Chciałbym dodać do tego, co mówi @Visionscaper na górze:

Third --> First --> object --> Second --> object

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

  • klasa C
  • plus połączenie
    • linearyzacja jego rodziców P1, P2, .. = L (P1, P2, ...) i
    • lista jego rodziców P1, P2, ..

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:

    L(O)  := [O]  // the linearization(mro) of O(object), because O has no parents

    L(First)  :=  [First] + merge(L(O), [O])
               =  [First] + merge([O], [O])
               =  [First, O]

    // Similarly, 
    L(Second)  := [Second, O]

    L(Third)   := [Third] + merge(L(First), L(Second), [First, Second])
                = [Third] + merge([First, O], [Second, O], [First, Second])
// class First is a good candidate for the first merge step, because it only appears as the head of the first and last lists
// class O is not a good candidate for the next merge step, because it also appears in the tails of list 1 and 2, 
                = [Third, First] + merge([O], [Second, O], [Second])
// class Second is a good candidate for the second merge step, because it appears as the head of the list 2 and 3
                = [Third, First, Second] + merge([O], [O])            
                = [Third, First, Second, O]

Zatem dla implementacji super () w następującym kodzie:

class First(object):
  def __init__(self):
    super(First, self).__init__()
    print "first"

class Second(object):
  def __init__(self):
    super(Second, self).__init__()
    print "second"

class Third(First, Second):
  def __init__(self):
    super(Third, self).__init__()
    print "that's it"

staje się oczywiste, jak ta metoda zostanie rozwiązana

Third.__init__() ---> First.__init__() ---> Second.__init__() ---> 
Object.__init__() ---> returns ---> Second.__init__() -
prints "second" - returns ---> First.__init__() -
prints "first" - returns ---> Third.__init__() - prints "that's it"
supi
źródło
„raczej dlatego, że Second pojawia się w pozycji głowy i nie pojawia się w pozycji ogona w podzbiorze hierarchii”. Nie jest jasne, jaka jest pozycja głowy lub ogona, ani co to jest podzbiór hierarchii, ani do którego podzestawu się odwołujesz.
OrangeSherbet
Pozycja ogona odnosi się do klas, które są wyższe w hierarchii klas i odwrotnie. „Obiekt” klasy podstawowej znajduje się na końcu ogona. Kluczem do zrozumienia algorytmu mro jest to, jak „Second” pojawia się jako super „First”. Zwykle zakładalibyśmy, że jest to klasa „obiektowa”. To prawda, ale tylko w perspektywie klasy „pierwszej”. Jednak patrząc z perspektywy „trzeciej klasy”, kolejność hierarchii dla „pierwszej” jest inna i jest obliczana jak pokazano powyżej. Algorytm mro próbuje utworzyć tę perspektywę (lub podzbiór hierarchii) dla wszystkich wielu klas odziedziczonych
supi
3

W python 3.5+ dziedziczenie wygląda dla mnie przewidywalnie i bardzo miło. Proszę spojrzeć na ten kod:

class Base(object):
  def foo(self):
    print("    Base(): entering")
    print("    Base(): exiting")


class First(Base):
  def foo(self):
    print("   First(): entering Will call Second now")
    super().foo()
    print("   First(): exiting")


class Second(Base):
  def foo(self):
    print("  Second(): entering")
    super().foo()
    print("  Second(): exiting")


class Third(First, Second):
  def foo(self):
    print(" Third(): entering")
    super().foo()
    print(" Third(): exiting")


class Fourth(Third):
  def foo(self):
    print("Fourth(): entering")
    super().foo()
    print("Fourth(): exiting")

Fourth().foo()
print(Fourth.__mro__)

Wyjścia:

Fourth(): entering
 Third(): entering
   First(): entering Will call Second now
  Second(): entering
    Base(): entering
    Base(): exiting
  Second(): exiting
   First(): exiting
 Third(): exiting
Fourth(): exiting
(<class '__main__.Fourth'>, <class '__main__.Third'>, <class '__main__.First'>, <class '__main__.Second'>, <class '__main__.Base'>, <class 'object'>)

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

rfedorov
źródło
2

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.

from django.utils.decorators import method_decorator
from django.contrib.auth.decorators import login_required

class LoginToPost:
    @method_decorator(login_required)
    def post(self, arg, *args, **kwargs):
        super().post(arg, *args, **kwargs)

Podobnie w przypadku innych metod.

Na liście spadkowej moich konkretnych zajęć dodawałbym moje LoginToPostprzed ListCreateAPIViewi LoginToPutOrDeleteprzed RetrieveUpdateDestroyAPIView. Moje konkretne zajęcia getpozostałyby bez dekoracji.

mariotomo
źródło
1

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.

    A
   / \
  B   C
   \ /
    D

Przykładowy fragment kodu to:

class A:
    def __init__(self, name=None):
        #  this is the head of the diamond, no need to call super() here
        self.name = name

class B(A):
    def __init__(self, param1='hello', **kwargs):
        super().__init__(**kwargs)
        self.param1 = param1

class C(A):
    def __init__(self, param2='bye', **kwargs):
        super().__init__(**kwargs)
        self.param2 = param2

class D(B, C):
    def __init__(self, works='fine', **kwargs):
        super().__init__(**kwargs)
        print(f"{works=}, {self.param1=}, {self.param2=}, {self.name=}")

d = D(name='Testing')

Oto klasa A. object

Akhil Nadh PC
źródło
1
Apowinien również dzwonić __init__. Anie „wymyślił” tej metody __init__, więc nie można założyć, że niektóre inne klasy mogły mieć Awcześniej w swoim MRO. Jedyną klasą, której __init__metoda nie wywołuje (i nie powinna) wywoływać, super().__init__jest object.
chepner
Tak. Właśnie dlatego napisałem „A” objectMoże myślę, że powinienem class A (object) : zamiast tego napisać
Akhil Nadh PC
Anie może być, objectjeśli dodajesz parametr do jego __init__.
chepner