Pytanie Zrozumienie superprogramowania Python () metodami __init __ () [duplicate]


To pytanie już zawiera odpowiedź:

Próbuję zrozumieć użycie super(). Z jego wyglądu można stworzyć obie klasy dziecięce.

Jestem ciekawy co do faktycznej różnicy między następującymi 2 klasami potomnymi.

class Base(object):
    def __init__(self):
        print "Base created"

class ChildA(Base):
    def __init__(self):
        Base.__init__(self)

class ChildB(Base):
    def __init__(self):
        super(ChildB, self).__init__()

ChildA() 
ChildB()

1982
2018-02-23 00:30


pochodzenie




Odpowiedzi:


super() pozwala uniknąć bezpośredniego odwoływania się do klasy bazowej, co może być miłe. Ale główną zaletą jest wielokrotne dziedziczenie, gdzie wszystkie rodzaje zabawne rzeczy może się zdarzyć. Zobacz standardowe dokumenty na super jeśli jeszcze tego nie zrobiłeś.

Zauważ, że składnia została zmieniona w Pythonie 3.0: możesz po prostu powiedzieć super().__init__() zamiast super(ChildB, self).__init__() które IMO jest trochę ładniejsze.


1427
2018-02-23 00:37



Czy możesz podać przykład super() używane z argumentami? - Steven Vascellaro
Czy możesz wyjaśnić? super(ChildB, self).__init__() to, co robi ChildB i self mieć do czynienia z super - rimiro


próbuję zrozumieć super()

Przyczyna, z której korzystamy super jest tak, że klasy potomne, które mogą używać koherentnego dziedziczenia wielokrotnego, wywołają poprawną funkcję kolejnej klasy macierzystej w metodzie MRO (Method Resolution Order).

W Pythonie 3 możemy to tak nazwać:

class ChildB(Base):
    def __init__(self):
        super().__init__() 

W Pythonie 2 musimy używać go w następujący sposób:

        super(ChildB, self).__init__()

Bez super, masz ograniczone możliwości korzystania z dziedziczenia wielokrotnego:

        Base.__init__(self) # Avoid this.

Poniżej wyjaśniam.

"Jaka jest różnica w tym kodzie?"

class ChildA(Base):
    def __init__(self):
        Base.__init__(self)

class ChildB(Base):
    def __init__(self):
        super(ChildB, self).__init__()
        # super().__init__() # you can call super like this in Python 3!

Podstawową różnicą w tym kodzie jest to, że dostajesz warstwę pośrednią w __init__ z super, która używa obecnej klasy do określenia następnej klasy __init__ sprawdzić w MRO.

I ilustruje tę różnicę w odpowiedzi na pytanie kanoniczne, Jak używać "super" w Pythonie?, co pokazuje iniekcja zależności i spółdzielcze wielokrotne dziedziczenie.

Jeśli Python nie ma super

Oto kod, który jest dokładnie odpowiednikiem super (w jaki sposób jest zaimplementowany w C, minus pewne sprawdzanie i zachowanie typu fallback i przetłumaczony na język Python):

class ChildB(Base):
    def __init__(self):
        mro = type(self).mro()             # Get the Method Resolution Order.
        check_next = mro.index(ChildB) + 1 # Start looking after *this* class.
        while check_next < len(mro):
            next_class = mro[check_next]
            if '__init__' in next_class.__dict__:
                next_class.__init__(self)
                break
            check_next += 1

Napisane trochę bardziej jak natywny Python:

class ChildB(Base):
    def __init__(self):
        mro = type(self).mro()
        for next_class in mro[mro.index(ChildB) + 1:]: # slice to end
            if hasattr(next_class, '__init__'):
                next_class.__init__(self)
                break

Gdybyśmy nie mieli super obiekt, musielibyśmy napisać ten ręczny kod wszędzie (lub odtworzyć go!), aby upewnić się, że nazwiemy właściwą następną metodę w Rozkazie Zarządzania metodą!

W jaki sposób super robi to w Pythonie 3 bez wyraźnego wskazania, która klasa i instancja z metody, z której została wywołana?

Otrzymuje ramkę stosu wywołań i znajduje klasę (niejawnie zapisaną jako lokalna zmienna wolna, __class__, czyniąc funkcję wywołującą zamknięciem klasy) i pierwszym argumentem tej funkcji, która powinna być instancją lub klasą, która informuje o tym, którego z nich ma użyć metoda MRO (Resolution Order Order).

Ponieważ wymaga to pierwszego argumentu dla MRO, za pomocą super przy użyciu metod statycznych jest niemożliwe.

Krytyka innych odpowiedzi:

super () pozwala ci uniknąć bezpośredniego odwoływania się do klasy bazowej, co może być miłe. . Ale główną zaletą jest wielokrotne dziedziczenie, w którym mogą się pojawiać wszelkiego rodzaju fajne rzeczy. Zobacz standardowe dokumenty na super, jeśli jeszcze tego nie zrobiłeś.

Jest raczej falisty i nie mówi nam wiele, ale chodzi o to super nie należy unikać pisania klasy rodzica. Chodzi o to, aby wywołać kolejną metodę zgodną z kolejnością rozstrzygania metod (MRO). Staje się to ważne w przypadku dziedziczenia wielokrotnego.

Wyjaśnię tutaj.

class Base(object):
    def __init__(self):
        print("Base init'ed")

class ChildA(Base):
    def __init__(self):
        print("ChildA init'ed")
        Base.__init__(self)

class ChildB(Base):
    def __init__(self):
        print("ChildB init'ed")
        super(ChildB, self).__init__()

I stwórzmy zależność, którą chcemy nazwać po dziecku:

class UserDependency(Base):
    def __init__(self):
        print("UserDependency init'ed")
        super(UserDependency, self).__init__()

Teraz pamiętaj, ChildB używa super, ChildA nie:

class UserA(ChildA, UserDependency):
    def __init__(self):
        print("UserA init'ed")
        super(UserA, self).__init__()

class UserB(ChildB, UserDependency):
    def __init__(self):
        print("UserB init'ed")
        super(UserB, self).__init__()

I UserA nie wywołuje metody UserDependency:

>>> UserA()
UserA init'ed
ChildA init'ed
Base init'ed
<__main__.UserA object at 0x0000000003403BA8>

Ale UserB, bo ChildB używa super, robi!:

>>> UserB()
UserB init'ed
ChildB init'ed
UserDependency init'ed
Base init'ed
<__main__.UserB object at 0x0000000003403438>

Krytyka za inną odpowiedź

W żadnym wypadku nie powinieneś wykonywać następujących czynności, co sugeruje inna odpowiedź, ponieważ na pewno otrzymasz błędy w podklasie ChildB:

        super(self.__class__, self).__init__() # Don't do this. Ever.

(Ta odpowiedź nie jest sprytna ani szczególnie interesująca, ale pomimo bezpośredniej krytyki w komentarzach i ponad 17 pochmurnych scenach, osoba udzielająca odpowiedzi upierała się, by sugerować, dopóki uprzejmy redaktor nie rozwiąże problemu.)

Objaśnienie: Ta odpowiedź sugerowała wywołanie super w ten sposób:

super(self.__class__, self).__init__()

To jest całkowicie źle. super pozwala nam wyszukać następny rodzic w MRO (zobacz pierwszą sekcję tej odpowiedzi) dla klas potomnych. Jeśli powiesz super jesteśmy w metodzie instancji podrzędnej, wtedy będzie szukać następnej metody w linii (prawdopodobnie tej) skutkującej rekursją, prawdopodobnie powodując logiczną awarię (w przykładzie odbierającego) lub RuntimeError gdy głębokość rekursji zostanie przekroczona.

>>> class Polygon(object):
...     def __init__(self, id):
...         self.id = id
...
>>> class Rectangle(Polygon):
...     def __init__(self, id, width, height):
...         super(self.__class__, self).__init__(id)
...         self.shape = (width, height)
...
>>> class Square(Rectangle):
...     pass
...
>>> Square('a', 10, 10)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in __init__
TypeError: __init__() missing 2 required positional arguments: 'width' and 'height'

413
2017-11-25 19:00



Nadal będę musiał to przemyśleć super() jednak ta funkcja jest wyraźnie najlepsza pod względem głębi i szczegółów. Bardzo doceniam również krytykę w odpowiedzi. Pomaga także lepiej zrozumieć tę koncepcję, identyfikując pułapki w innych odpowiedziach. Dziękuję Ci ! - Yohan Obadia
@Aaron Hall, dzięki za tak szczegółowe informacje. Myślę, że powinna istnieć jeszcze jedna opcja dla mentorów, aby nazwać jakąś odpowiedź jako nieodpowiednią lub niekompletną, jeśli nie podadzą prawidłowych dostatecznych informacji. - hunch
Dzięki, to było super pomocne. Krytyka słabego / niewłaściwego użycia była bardzo ilustracyjna, dlaczego i jak używać super - Xarses
Bardzo dobre wyjaśnienie dziedziczenia Pythona. Dzięki - ioaniatr


Zauważono, że w Pythonie 3.0+ możesz używać

super().__init__() 

aby wykonać połączenie, które jest zwięzłe i nie wymaga od użytkownika bezpośredniego odwoływania się do nadrzędnych nazw klas OR, co może być przydatne. Chcę tylko dodać, że w Pythonie 2.7 lub pod, można zapisać zachowanie niewrażliwe na nazwy self.__class__ zamiast nazwy klasy, np.

super(self.__class__, self).__init__()

JEDNAK, to zrywa telefony do super dla wszystkich klas, które odziedziczą po klasie, gdzie self.__class__ może zwrócić klasę potomną. Na przykład:

class Polygon(object):
    def __init__(self, id):
        self.id = id

class Rectangle(Polygon):
    def __init__(self, id, width, height):
        super(self.__class__, self).__init__(id)
        self.shape = (width, height)

class Square(Rectangle):
    pass

Tutaj mam zajęcia Square, która jest podklasą Rectangle. Powiedzmy, że nie chcę pisać osobnego konstruktora dla Square ponieważ konstruktor dla Rectangle jest wystarczająco dobre, ale z jakiegoś powodu chcę zaimplementować Kwadrat, więc mogę ponownie zastosować inną metodę.

Kiedy tworzę Square za pomocą mSquare = Square('a', 10,10), Python wywołuje konstruktor dla Rectangle ponieważ nie dałem Square własny konstruktor. Jednak w konstruktorze dla Rectangle, telefon super(self.__class__,self) zwróci superklasę mSquare, więc wywołuje konstruktor dla Rectangle jeszcze raz. Tak właśnie dzieje się nieskończona pętla, o czym wspomniał @S_C. W tym przypadku, kiedy biegnę super(...).__init__() Dzwonię do konstruktora Rectangle ale ponieważ nie daję żadnych argumentów, dostanę błąd.


223
2017-10-08 20:08



Co sugeruje ta odpowiedź, super(self.__class__, self).__init__() nie działa, jeśli ponownie tworzysz podklasę bez dodawania nowego __init__. Wtedy masz nieskończoną rekurencję. - glglgl
Ta odpowiedź jest absurdalna. Jeśli zamierzasz nadużyć w ten sposób, równie dobrze możesz po prostu zakodować nazwę klasy podstawowej. Jest mniej nie tak. Punktem pierwszego argumentu super jest to, że jest nie koniecznie rodzaj jaźni. Proszę przeczytać "super uważany super" przez Rhettinger (lub obejrzeć niektóre z jego filmów). - Veky
Skróty przedstawione tutaj w Pythonie 2 mają już wspomniane pułapki. Nie używaj tego, bo twój kod złamie się w sposób, którego nie możesz przewidzieć. Ten "przydatny skrót" łamie się super, ale możesz nie zdawać sobie z tego sprawy, dopóki nie poświęcisz dużo czasu na debugowanie. Użyj Pythona 3, jeśli super jest zbyt szczegółowe. - Ryan Hiebert
Edytowano odpowiedź. Przepraszam, jeśli ta zmiana zmienia znaczenie o 180 stopni, ale teraz ta odpowiedź powinna mieć jakiś sens. - Tino
Nie ma sensu mówić komuś, że mogą zrobić coś, co jest banalnie zademonstrowane jako niepoprawne. Możesz alias echo do python. Nikt nigdy by tego nie zasugerował! - Aaron Hall♦


Super nie ma skutków ubocznych

Base = ChildB

Base()

działa zgodnie z oczekiwaniami

Base = ChildA

Base()

dostaje się do nieskończonej rekursji.


75
2017-11-27 23:26



The super sam wywołanie nie ma skutków ubocznych, po prostu tworzy obiekt proxy. Nie jestem pewien, o czym stara się powiedzieć ta odpowiedź super w przeciwieństwie do bezpośredniej klasy macierzystej. - davidism
Stwierdzenie "Super nie ma skutków ubocznych" nie ma sensu w tym kontekście. Super po prostu gwarantuje, że nazwiemy poprawną metodę następnej klasy w kolejności rozwiązywania metod, podczas gdy w inny sposób zakodujemy kodowanie następnej metody, co sprawia, że ​​kojarzenie wielu dziedziczeń jest trudniejsze. - Aaron Hall♦


Po prostu heads up ... z Pythonem 2.7 i od tego czasu wierzę super() został wprowadzony w wersji 2.2, można tylko zadzwonić super()jeśli jeden z rodziców dziedziczy po klasie, która ostatecznie dziedziczy object (zajęcia w nowym stylu).

Osobiście, tak jak w przypadku kodu Pythona 2.7, zamierzam dalej używać BaseClassName.__init__(self, args) dopóki nie uzyskałem przewagi w użyciu super().


68
2018-05-25 17:52



bardzo dobry punkt. JEŚLI nie mówisz wyraźnie: class Base (object): wtedy otrzymasz błąd w następujący sposób: "TypeError: musi być type, not classobj" - andilabs
@i dostałem ten błąd na drugi dzień i w końcu po prostu zrezygnowałem próbując to rozgryźć. Po prostu bawiłem się na iPythonie. Co za koszmarny koszmar złego komunikatu o błędzie, jeśli to był kod, który musiałem debugować! - Two-Bit Alchemist


Tak naprawdę nie jest. super() patrzy na następną klasę w MRO (kolejność rozwiązywania metod, dostępna z cls.__mro__), aby wywołać metody. Po prostu dzwonię do bazy __init__ wzywa bazę __init__. Tak się składa, że ​​MRO ma dokładnie jeden element - bazę. Więc robisz dokładnie to samo, ale w przyjemniejszy sposób super() (szczególnie, jeśli później dostaniesz dziedziczenie wielokrotne).


46
2018-02-23 00:34



Widzę. Czy mógłbyś trochę wyjaśnić, dlaczego jego ładniejsza jest funkcja super () z wieloma dziedziczeniami? Dla mnie baza .__ init __ (self) jest krótsza (czystsza). Gdybym miał dwie klasy bazowe, byłyby to dwie linie lub dwie linie super (). A może źle zrozumiałem, co masz na myśli mówiąc "ładniej"? - Mizipzor
W rzeczywistości byłaby to jedna linia super (). Kiedy masz dziedziczenie wielokrotne, MRO jest nadal płaski. Tak więc pierwsze wywołanie super () .__ init__ wywołuje następną klasę w tym, która następnie wywołuje następną, i tak dalej. Powinieneś naprawdę sprawdzić kilka dokumentów na ten temat. - Devin Jeanpierre
Klasa potomna MRO również zawiera obiekt - MRO klasy jest widoczny w mro zmienna klasy. - James Brady
Zwróć też uwagę, że klasy klasyczne (pre 2.2) nie obsługują super - musisz wyraźnie odwoływać się do klas bazowych. - James Brady
"Klasa potomna MRO również zawiera obiekt - MRO klasy jest widoczny w mro zmienna klasy. "To jest duża oops. - Devin Jeanpierre


Główna różnica polega na tym ChildA.__init__ zadzwoni bezwarunkowo Base.__init__ natomiast ChildB.__init__ zadzwonię __init__ w jakakolwiek klasa się dzieje ChildB przodek w selflinia przodków (które mogą różnić się od oczekiwanych).

Jeśli dodasz ClassC który używa wielokrotnego dziedziczenia:

class Mixin(Base):
  def __init__(self):
    print "Mixin stuff"
    super(Mixin, self).__init__()

class ChildC(ChildB, Mixin):  # Mixin is now between ChildB and Base
  pass

ChildC()
help(ChildC) # shows that the the Method Resolution Order is ChildC->ChildB->Mixin->Base

następnie Base nie jest już rodzicem ChildB dla ChildC instancje. Teraz super(ChildB, self) wskaże Mixin gdyby self jest ChildC instancja.

Włożyłeś Mixin pomiędzy ChildB i Base. I możesz z tego skorzystać super()

Więc jeśli zaprojektowałeś swoje klasy, aby mogły być używane w scenariuszu kooperatywnego wielokrotnego dziedziczenia, używaj super ponieważ tak naprawdę nie wiesz, kto będzie przodkiem w czasie wykonywania.

The super uważany super post i wideo towarzyszące pycon 2015 wyjaśnij to całkiem dobrze.


22
2017-09-21 06:41



To. Znaczenie super(ChildB, self) zmienia się w zależności od MRO obiektu, o którym mowa self, które nie mogą być poznane przed uruchomieniem. Innymi słowy, autor ChildB nie ma możliwości dowiedzenia się co super() postanowi we wszystkich przypadkach, chyba że będą w stanie to zagwarantować ChildB nigdy nie zostaną poddane podklasie. - nispio