Vererbung und Überschreiben von __init__ in Python


129

Ich habe 'Dive Into Python' gelesen und im Kapitel über Klassen gibt es dieses Beispiel:

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

Der Autor sagt dann, wenn Sie die __init__Methode überschreiben möchten , müssen Sie das übergeordnete Element explizit __init__mit den richtigen Parametern aufrufen .

  1. Was wäre, wenn diese FileInfoKlasse mehr als eine Ahnenklasse hätte?
    • Muss ich alle __init__Methoden der Ahnenklassen explizit aufrufen ?
  2. Muss ich dies auch mit einer anderen Methode tun, die ich überschreiben möchte?

3
Beachten Sie, dass das Überladen ein vom Überschreiben getrenntes Konzept ist.
Dana the Sane

Antworten:


157

Das Buch ist in Bezug auf das Aufrufen von Unterklassen und Oberklassen etwas veraltet. Es ist auch ein wenig veraltet in Bezug auf die Unterklasse der integrierten Klassen.

Es sieht heutzutage so aus:

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

Beachte das Folgende:

  1. Sie können direkt integrierten Klassen Unterklasse, wie dict, list, tupleusw.

  2. Die superFunktion behandelt das Aufspüren der Oberklassen dieser Klasse und das entsprechende Aufrufen von Funktionen.


5
soll ich nach einem besseren Buch / Tutorial suchen?
Liewl

2
Findet super () bei Mehrfachvererbung alle in Ihrem Namen auf?
Dana

dikt .__ init __ (self), aber nichts ist falsch daran - der super (...) Aufruf bietet nur eine konsistentere Syntax. (Ich bin nicht sicher, wie es für Mehrfachvererbung funktioniert, ich denke, es kann nur eine Superklasse init finden )
David Z

4
Die Absicht von super () ist, dass es Mehrfachvererbung behandelt. Der Nachteil ist, dass in der Praxis die Mehrfachvererbung immer noch sehr leicht bricht (siehe < fuhm.net/super-harmful ).
etw

2
Ja, wenn mehrere Vererbungs- und Basisklassen Konstruktorargumente verwenden, rufen Sie die Konstruktoren normalerweise manuell auf.
Torsten Marek

18

In jeder Klasse, von der Sie erben müssen, können Sie eine Schleife für jede Klasse ausführen, die beim Initiieren der untergeordneten Klasse initialisiert werden muss. Ein Beispiel, das kopiert werden kann, ist möglicherweise besser zu verstehen.

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

2
Es scheint nicht so, als ob die for-Schleife Child.__init__notwendig wäre. Wenn ich es aus dem Beispiel entferne, druckt mein Kind immer noch "Oma". Wird der Großeltern-Init nicht von der ParentKlasse behandelt?
Adam

4
Ich denke, Granparents Init's werden bereits von Parent's Init behandelt, nicht wahr?
johk95

15

Es ist nicht wirklich haben , um den Anruf __init__Methoden der Basisklasse (n), aber Sie in der Regel wollen , es zu tun , weil die Basisklassen werden einige wichtige Initialisierungen tun es , die für die restlichen Klassen Methoden zur Arbeit benötigt werden.

Bei anderen Methoden hängt es von Ihren Absichten ab. Wenn Sie dem Verhalten der Basisklassen nur etwas hinzufügen möchten, müssen Sie die Basisklassenmethode zusätzlich zu Ihrem eigenen Code aufrufen. Wenn Sie das Verhalten grundlegend ändern möchten, rufen Sie möglicherweise nicht die Methode der Basisklasse auf und implementieren alle Funktionen direkt in der abgeleiteten Klasse.


4
Der technischen Vollständigkeit halber werfen einige Klassen, wie Threading.Thread, gigantische Fehler auf, wenn Sie jemals versuchen, das Aufrufen des Init des Elternteils zu vermeiden .
David Berger

5
Ich finde das ganze Gespräch "Sie müssen nicht den Konstruktor der Basis anrufen" äußerst irritierend. Sie müssen es nicht in einer Sprache nennen, die ich kenne. Alle von ihnen werden auf die gleiche Weise Fehler machen (oder nicht), indem sie keine Mitglieder initialisieren. Der Vorschlag, Basisklassen nicht zu initialisieren, ist in vielerlei Hinsicht falsch. Wenn eine Klasse jetzt keine Initialisierung benötigt, wird sie diese in Zukunft benötigen. Der Konstruktor ist Teil der Schnittstelle der Klassenstruktur / des Sprachkonstrukts und sollte korrekt verwendet werden. Es ist richtig, es irgendwann im Konstruktor Ihres Abgeleiteten aufzurufen. Also mach es.
AndreasT

2
"Es ist in vielerlei Hinsicht falsch, Basisklassen nicht zu initialisieren." Niemand hat vorgeschlagen, die Basisklasse nicht zu initialisieren. Lesen Sie die Antwort sorgfältig durch. Es geht nur um Absicht. 1) Wenn Sie die Init-Logik der Basisklasse unverändert lassen möchten, überschreiben Sie die Init-Methode in Ihrer abgeleiteten Klasse nicht. 2) Wenn Sie die Init-Logik von der Basisklasse aus erweitern möchten, definieren Sie Ihre eigene Init-Methode und rufen dann die Init-Methode der Basisklasse auf. 3) Wenn Sie die Init-Logik der Basisklasse ersetzen möchten, definieren Sie Ihre eigene Init-Methode, ohne die aus der Basisklasse aufzurufen.
Wombatonfire

4

Wenn die FileInfo-Klasse mehr als eine Vorfahrenklasse hat, sollten Sie auf jeden Fall alle ihre __init __ () -Funktionen aufrufen. Sie sollten dasselbe auch für die Funktion __del __ () tun, die ein Destruktor ist.


2

Ja, Sie müssen __init__für jede übergeordnete Klasse anrufen . Gleiches gilt für Funktionen, wenn Sie eine Funktion überschreiben, die in beiden Elternteilen vorhanden ist.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.