Dziedziczenie i przesłanianie __init__ w Pythonie

131

Czytałem „Dive Into Python” iw rozdziale poświęconym klasom podaje ten przykład:

class FileInfo(UserDict):
    "store file metadata"
    def __init__(self, filename=None):
        UserDict.__init__(self)
        self["name"] = filename

Następnie autor mówi, że jeśli chcesz przesłonić __init__metodę, musisz jawnie wywołać rodzica __init__z poprawnymi parametrami.

  1. A co by FileInfobyło, gdyby ta klasa miała więcej niż jedną klasę przodków?
    • Czy muszę jawnie wywoływać wszystkie __init__metody klas nadrzędnych?
  2. Czy muszę to zrobić w innej metodzie, którą chcę zastąpić?
liewl
źródło
3
Należy zauważyć, że przeciążanie jest pojęciem odrębnym od zastępowania.
Dana the Sane

Odpowiedzi:

159

Książka jest nieco przestarzała w odniesieniu do wywołań podklasy i nadklasy. Jest też trochę przestarzały w odniesieniu do podklas wbudowanych w klasy.

Obecnie wygląda to tak:

class FileInfo(dict):
    """store file metadata"""
    def __init__(self, filename=None):
        super(FileInfo, self).__init__()
        self["name"] = filename

Zwróć uwagę na następujące kwestie:

  1. Możemy bezpośrednio podklasy wbudowanych klas, jak dict, list, tuple, itd.

  2. Te superuchwyty funkcja śledzenia superklas tej klasy i wywołanie funkcji w nich odpowiednio.

S.Lott
źródło
5
czy powinienem poszukać lepszej książki / samouczka?
liewl
2
Więc w przypadku wielokrotnego dziedziczenia, czy super () śledzi je wszystkie w twoim imieniu?
Dana
dict .__ init __ (self), właściwie, ale nie ma w tym nic złego - wywołanie super (...) zapewnia po prostu bardziej spójną składnię. (Nie jestem pewien, jak to działa w przypadku wielokrotnego dziedziczenia, myślę, że może znaleźć tylko jeden init nadklasy )
David Z
4
Intencją super () jest obsługa dziedziczenia wielokrotnego. Wadą jest to, że w praktyce dziedziczenie wielokrotne nadal bardzo łatwo się psuje (patrz < fuhm.net/super-harmful ).
sth
2
Tak, w przypadku wielokrotnego dziedziczenia i klas podstawowych pobierających argumenty konstruktorów, zwykle zdarza się, że konstruktory są wywoływane ręcznie.
Torsten Marek
18

W każdej klasie, z której musisz dziedziczyć, możesz uruchomić pętlę każdej klasy, która wymaga zainicjowania po zainicjowaniu klasy podrzędnej ... przykład, który można skopiować, może być lepiej zrozumiany ...

class Female_Grandparent:
    def __init__(self):
        self.grandma_name = 'Grandma'

class Male_Grandparent:
    def __init__(self):
        self.grandpa_name = 'Grandpa'

class Parent(Female_Grandparent, Male_Grandparent):
    def __init__(self):
        Female_Grandparent.__init__(self)
        Male_Grandparent.__init__(self)

        self.parent_name = 'Parent Class'

class Child(Parent):
    def __init__(self):
        Parent.__init__(self)
#---------------------------------------------------------------------------------------#
        for cls in Parent.__bases__: # This block grabs the classes of the child
             cls.__init__(self)      # class (which is named 'Parent' in this case), 
                                     # and iterates through them, initiating each one.
                                     # The result is that each parent, of each child,
                                     # is automatically handled upon initiation of the 
                                     # dependent class. WOOT WOOT! :D
#---------------------------------------------------------------------------------------#



g = Female_Grandparent()
print g.grandma_name

p = Parent()
print p.grandma_name

child = Child()

print child.grandma_name
Błąd kodu
źródło
2
Wygląda na to, że pętla for nie Child.__init__jest konieczna. Po usunięciu go z przykładu dziecko nadal drukuje napis „Babcia”. Czy dziadkowie nie są obsługiwani przez Parentklasę?
Adam
4
Myślę, że init babci i rodziców jest już obsługiwany przez init Parent, prawda?
johk95,
15

Naprawdę nie musisz wywoływać __init__metod klas bazowych, ale zwykle chcesz to zrobić, ponieważ klasy podstawowe wykonają tam ważne inicjalizacje, które są potrzebne do działania pozostałych metod klas.

W przypadku innych metod zależy to od Twoich intencji. Jeśli chcesz tylko dodać coś do zachowania klas bazowych, będziesz chciał wywołać metodę klas bazowych dodatkowo do własnego kodu. Jeśli chcesz zasadniczo zmienić zachowanie, możesz nie wywoływać metody klasy bazowej i zaimplementować wszystkich funkcji bezpośrednio w klasie pochodnej.

sth
źródło
4
Ze względów technicznych niektóre klasy, takie jak Threading.Thread, będą generować gigantyczne błędy, jeśli kiedykolwiek spróbujesz uniknąć wywołania init rodzica .
David Berger
5
To wszystko „nie musisz wzywać konstruktora bazy” bardzo mnie irytuje. Nie musisz tego mówić w żadnym języku, który znam. Wszystkie zepsują (lub nie) w podobny sposób, nie inicjalizując członków. Sugerowanie, aby nie inicjować klas bazowych, jest po prostu błędne z wielu powodów. Jeśli klasa nie wymaga teraz inicjalizacji, będzie potrzebować jej w przyszłości. Konstruktor jest częścią interfejsu struktury klasy / konstrukcji języka i powinien być używany poprawnie. Prawidłowym użyciem jest wywołanie go kiedyś w konstruktorze twojego pochodnego. Więc zrób to.
AndreasT,
2
„Sugerowanie, aby nie inicjować klas bazowych, jest po prostu błędne z wielu powodów”. Nikt nie zasugerował, aby nie inicjować klasy bazowej. Przeczytaj uważnie odpowiedź. Chodzi o intencję. 1) Jeśli chcesz pozostawić logikę init klasy bazowej bez zmian, nie nadpisujesz metody init w swojej klasie pochodnej. 2) Jeśli chcesz rozszerzyć logikę init z klasy bazowej, definiujesz własną metodę init, a następnie wywołujesz z niej metodę init klasy bazowej. 3) Jeśli chcesz zastąpić logikę init klasy bazowej, definiujesz własną metodę init bez wywoływania metody z klasy bazowej.
wombatonfire
4

Jeśli klasa FileInfo ma więcej niż jedną klasę nadrzędną, zdecydowanie powinieneś wywołać wszystkie ich funkcje __init __ (). Powinieneś również zrobić to samo dla funkcji __del __ (), która jest destruktorem.

moinudin
źródło
2

Tak, musisz wezwać __init__każdą klasę rodzicielską. To samo dotyczy funkcji, jeśli zastępujesz funkcję istniejącą u obojga rodziców.

vezult
źródło