Czy __init __ () powinien wywoływać metodę __init __ () klasy nadrzędnej?


133

Użyłem, że w Objective-C mam tę konstrukcję:

- (void)init {
    if (self = [super init]) {
        // init class
    }
    return self;
}

Czy Python powinien również wywoływać implementację klasy nadrzędnej dla __init__?

class NewClass(SomeOtherClass):
    def __init__(self):
        SomeOtherClass.__init__(self)
        # init class

Czy to również prawda / fałsz dla __new__()i __del__()?

Edycja: Jest bardzo podobne pytanie: dziedziczenie i zastępowanie __init__w Pythonie


znacząco zmieniłeś swój kod. Rozumiem, że oryginał objectbył literówką. Ale teraz nie masz nawet supertytułu swojego pytania, do którego się odnosi.
SilentGhost

Pomyślałem, że nazwa super jest używana jako nazwa klasy nadrzędnej. Nie sądziłem, że ktokolwiek pomyśli o tej funkcji. Przepraszam za wszelkie nieporozumienia.
Georg Schölly

Odpowiedzi:


68

W Pythonie wywołanie superklasy __init__jest opcjonalne. Jeśli to nazwiesz, opcjonalne jest również użycie superidentyfikatora lub jawne nazwanie superklasy:

object.__init__(self)

W przypadku obiektu wywołanie metody super nie jest bezwzględnie konieczne, ponieważ metoda super jest pusta. To samo dotyczy __del__.

Z drugiej strony, __new__rzeczywiście powinieneś wywołać super metodę i użyć jej zwrotu jako nowo utworzonego obiektu - chyba że jawnie chcesz zwrócić coś innego.


Więc nie ma konwencji, aby po prostu nazwać implementację super?
Georg Schölly

5
W klasach w starym stylu super init można było wywołać tylko wtedy, gdy super-klasa faktycznie miała zdefiniowany init (czego często nie ma). Dlatego ludzie zazwyczaj myślą o wywołaniu super metody, zamiast robić to z zasady.
Martin v. Löwis

1
Gdyby składnia w Pythonie była tak prosta, jak [super init], byłaby bardziej powszechna. Tylko spekulacyjna myśl; super konstrukcja w Pythonie 2.x jest dla mnie trochę niezręczna.
u0b34a0f6ae

Tutaj wydaje się być interesującym (i być może sprzecznym) przykładem: bytes.com/topic/python/answers/ ... init
mlvljr

„opcjonalny” w tym sensie, że nie musisz go nazywać, ale jeśli go nie nazwiesz, nie zostanie wywołany automatycznie.
McKay,

141

Jeśli __init__oprócz tego, co jest robione w bieżących zajęciach, __init__,musisz zrobić coś z super zajęć, musisz to nazwać samodzielnie, ponieważ nie stanie się to automatycznie. Ale jeśli nie potrzebujesz niczego od super, __init__,nie musisz tego nazywać. Przykład:

>>> class C(object):
        def __init__(self):
            self.b = 1


>>> class D(C):
        def __init__(self):
            super().__init__() # in Python 2 use super(D, self).__init__()
            self.a = 1


>>> class E(C):
        def __init__(self):
            self.a = 1


>>> d = D()
>>> d.a
1
>>> d.b  # This works because of the call to super's init
1
>>> e = E()
>>> e.a
1
>>> e.b  # This is going to fail since nothing in E initializes b...
Traceback (most recent call last):
  File "<pyshell#70>", line 1, in <module>
    e.b  # This is going to fail since nothing in E initializes b...
AttributeError: 'E' object has no attribute 'b'

__del__jest w ten sam sposób, (ale uważaj na __del__ostateczność - zamiast tego rozważ zrobienie tego za pomocą instrukcji with).

Rzadko używam __new__. , wykonuję całą inicjalizację w__init__.


3
Definicja klasy D (C) musi zostać poprawiona w ten sposóbsuper(D,self).__init__()
eyquem

12
super () .__ init __ () działa tylko w Pythonie 3. W Pythonie 2 potrzebujesz super (D, self) .__ init __ ()
Jacinda

1
„Jeśli potrzebujesz czegoś z inicjalizatora super …” - jest to bardzo problematyczne stwierdzenie, ponieważ nie chodzi o to, czy ty / podklasa potrzebuje „czegoś”, ale czy klasa bazowa potrzebuje czegoś, aby była poprawna instancja klasy bazowej i działa poprawnie. Jako implementator klasy pochodnej, elementy wewnętrzne klasy bazowej to rzeczy, których nie możesz / nie powinieneś wiedzieć, a nawet jeśli robisz, ponieważ napisałeś oba lub wewnętrzne są udokumentowane, projekt bazy może ulec zmianie w przyszłości i zepsuć się z powodu źle napisanego Klasy pochodnej. Dlatego zawsze upewnij się, że klasa bazowa została w pełni zainicjowana.
Nick

108

W odpowiedzi Anona:
„Jeśli potrzebujesz czegoś z superumiejętności __init__do zrobienia oprócz tego, co jest robione w bieżącej klasie __init__, musisz to nazwać sam, ponieważ nie stanie się to automatycznie”

To niewiarygodne: mówi dokładnie przeciwnie do zasady dziedziczenia.


Nie chodzi o to, że "coś z super __init__ (...) nie wydarzy się automatycznie" , chodzi o to, że BYŁO to automatycznie, ale nie dzieje się tak, ponieważ klasa bazowa ' __init__jest nadpisana przez definicję klas pochodnych__init__

Więc DLACZEGO definiowanie klasy pochodnej __init__, skoro zastępuje to, do czego jest skierowane, gdy ktoś ucieka się do dziedziczenia?

Dzieje się tak, ponieważ trzeba zdefiniować coś, czego NIE robi się w klasie bazowej ' __init__, a jedyną możliwością uzyskania tego jest umieszczenie jej wykonania w __init__funkcji klasy pochodnej' .
Innymi słowy, potrzeba czegoś w klasie bazowej „ __init__oprócz tego, co zostałoby zrobione automatycznie w klasie podstawowej”, __init__gdyby ta ostatnia nie została zastąpiona.
NIE przeciwnie.


Następnie problem polega na tym, że pożądane instrukcje obecne w klasie bazowej ' __init__nie są już aktywowane w momencie ich tworzenia. Aby zrównoważyć tę dezaktywację, wymagane jest coś specjalnego: jawne wywołanie klasy bazowej „ __init__w celu ZACHOWANIA , NIE DODAWANIA, inicjalizacji wykonywanej przez klasę bazową” __init__. Dokładnie to jest powiedziane w oficjalnym dokumencie:

Metoda przesłaniająca w klasie pochodnej może w rzeczywistości chcieć rozszerzyć, a nie po prostu zastąpić metodę klasy bazowej o tej samej nazwie. Istnieje prosty sposób bezpośredniego wywołania metody klasy bazowej: po prostu wywołaj BaseClassName.methodname (self, arguments).
http://docs.python.org/tutorial/classes.html#inheritance

To cała historia:

  • gdy celem jest ZACHOWANIE inicjalizacji wykonywanej przez klasę bazową, czyli czyste dziedziczenie, nic specjalnego nie jest potrzebne, należy po prostu unikać definiowania __init__funkcji w klasie pochodnej

  • gdy celem jest ZASTĄPIENIE inicjalizacji wykonywanej przez klasę bazową, __init__musi być zdefiniowana w klasie pochodnej

  • gdy celem jest dodanie procesów do inicjalizacji wykonywanej przez klasę bazową, __init__ należy zdefiniować klasę pochodną , zawierającą jawne wywołanie klasy bazowej__init__


To, co czuję zdumiewające na stanowisku Anona, to nie tylko to, że wyraża on przeciwieństwo teorii dziedziczenia, ale że było 5 facetów, którzy przeszli obok tego głosowania bez odwracania włosów, a ponadto nie było nikogo, kto zareagowałby przez 2 lata w wątek, którego interesujący temat trzeba czytać stosunkowo często.


1
Byłem pewien, że ten post zostanie pozytywnie rozpatrzony. Obawiam się, że nie będę miał wiele wyjaśnień na temat powodów. Łatwiej jest głosować przeciw, niż analizować tekst, który wydaje się niezrozumiały. Długo próbowałem zrozumieć post Anona, zanim w końcu zdałem sobie sprawę, że został on napisany zwodniczo i niezbyt autorytatywny. Może można to zinterpretować jako w przybliżeniu właściwe dla kogoś, kto wie o dziedziczeniu; ale wydaje mi się to dezorientujące, gdy czytane jest przez kogoś, kto ma niepewne pojęcie o dziedziczeniu, temat nie jest tak jasny jak woda skalna w ogóle
eyquem

2
„Byłem pewien, że ten post zostanie pozytywnie rozpatrzony ...” Głównym problemem jest to, że trochę się spóźniłeś na imprezę i większość ludzi może nie czytać więcej niż kilka pierwszych odpowiedzi.
Swoją

To świetna odpowiedź.
Trilarion

3
Brakowało Ci znaczących słów „dodatkowo” w zdaniu, które zacytowałeś z Aarona. Oświadczenie Aarona jest całkowicie poprawne i pasuje do tego, co powiesz.
GreenAsJade

1
To pierwsze wyjaśnienie, które sprawiło, że wybór projektu przez Pythona ma sens.
Joseph Garvin

20

Edycja : (po zmianie kodu)
Nie możemy powiedzieć Ci, czy chcesz zadzwonić do rodzica __init__(lub innej funkcji). Dziedziczenie oczywiście działałoby bez takiego wezwania. Wszystko zależy od logiki twojego kodu: na przykład, jeśli wszystko __init__robisz w klasie nadrzędnej, możesz po prostu __init__całkowicie pominąć klasę podrzędną .

rozważ następujący przykład:

>>> class A:
    def __init__(self, val):
        self.a = val


>>> class B(A):
    pass

>>> class C(A):
    def __init__(self, val):
        A.__init__(self, val)
        self.a += val


>>> A(4).a
4
>>> B(5).a
5
>>> C(6).a
12

Usunąłem super wywołanie z mojego przykładu, czy chciałem wiedzieć, czy należy wywołać implementację init klasy nadrzędnej, czy nie.
Georg Schölly,

możesz wtedy edytować tytuł. ale moja odpowiedź jest nadal aktualna.
SilentGhost

5

Nie ma twardej i szybkiej reguły. Dokumentacja klasy powinna wskazywać, czy podklasy powinny wywoływać metodę nadklasy. Czasami chcesz całkowicie zastąpić zachowanie superklasy, a innym razem je rozszerzyć - tj. Wywołać swój własny kod przed i / lub po wywołaniu nadklasy.

Aktualizacja: ta sama podstawowa logika ma zastosowanie do każdego wywołania metody. Konstruktory czasami wymagają szczególnej uwagi (ponieważ często ustawiają stan, który określa zachowanie), a destruktory, ponieważ są konstruktorami równoległymi (np. Przy alokacji zasobów, np. Połączeń z bazami danych). Ale to samo może dotyczyć, powiedzmy, render()metody widgetu.

Dalsza aktualizacja: co to jest OPP? Masz na myśli OOP? Nie - podklasa często musi wiedzieć coś o projekcie nadklasy. Nie wewnętrzne szczegóły implementacji - ale podstawowa umowa, którą nadklasa ma ze swoimi klientami (przy użyciu klas). Nie narusza to w żaden sposób zasad OOP. Dlatego protectedjest to ogólnie poprawne pojęcie w OOP (choć oczywiście nie w Pythonie).


Powiedziałeś, że czasami ktoś chciałby wywołać własny kod przed wywołaniem nadklasy. Aby to zrobić, potrzebna jest wiedza o implementacji klasy nadrzędnej, która naruszyłaby OPP.
Georg Schölly,

4

IMO, powinieneś to nazwać. Jeśli twoja superklasa jest object, nie powinieneś, ale w innych przypadkach myślę, że wyjątkowe jest nie nazywać tego. Jak już odpowiedzieli inni, jest to bardzo wygodne, jeśli twoja klasa nie musi nawet nadpisywać __init__samej siebie, na przykład gdy nie ma (dodatkowego) stanu wewnętrznego do zainicjowania.


2

Tak, zawsze powinieneś __init__jawnie wywoływać klasę bazową jako dobrą praktykę kodowania. Zapomnienie o tym może spowodować subtelne problemy lub błędy w czasie wykonywania. Dzieje się tak, nawet jeśli __init__nie przyjmuje żadnych parametrów. Jest to odmienne od innych języków, w których kompilator niejawnie wywoła za Ciebie konstruktor klasy bazowej. Python tego nie robi!

Głównym powodem zawsze wywoływania klasy bazowej _init__jest to, że klasa bazowa może zazwyczaj tworzyć zmienne składowe i inicjować je do wartości domyślnych. Więc jeśli nie wywołasz init klasy bazowej, żaden z tego kodu nie zostanie wykonany i otrzymasz klasę bazową, która nie ma zmiennych składowych.

Przykład :

class Base:
  def __init__(self):
    print('base init')

class Derived1(Base):
  def __init__(self):
    print('derived1 init')

class Derived2(Base):
  def __init__(self):
    super(Derived2, self).__init__()
    print('derived2 init')

print('Creating Derived1...')
d1 = Derived1()
print('Creating Derived2...')
d2 = Derived2()

To drukuje ...

Creating Derived1...
derived1 init
Creating Derived2...
base init
derived2 init

Uruchom ten kod .

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.