Eine Klasse, die auf einer anderen aufbaut
Mit Vererbung definierst du eine neue Klasse als Erweiterung einer vorhandenen. Die vorhandene Klasse ist die Elternklasse (auch Basisklasse oder Oberklasse genannt), die neue ist die Kindklasse (Unterklasse). Die Kindklasse bekommt jede Methode und jedes Klassenattribut der Elternklasse, ohne sie zu wiederholen, und kann eigene hinzufügen oder die ersetzen, die sich anders verhalten sollen.
Die Elternklasse gibst du in Klammern hinter dem Klassennamen an:
Ausgabe:
Rex is an animal
Woof
Dog hat weder ein eigenes __init__ noch ein eigenes describe. Wenn du Dog("Rex") aufrufst, sucht Python __init__ auf Dog, findet es nicht und nimmt das von Animal. Dieselbe Suche passiert bei rex.describe(). Bei rex.speak() findet Python speak zuerst auf Dog und hört dort auf.
Diese Suchreihenfolge ist der ganze Mechanismus: Python durchsucht die eigene Klasse des Objekts, dann die Elternklasse, dann deren Elternklasse, und nimmt den ersten Treffer. Wenn Klassen neu für dich sind, erklärt die Seite zu Klassen zuerst __init__, self und Methoden.
Eine Methode überschreiben
Eine Methode in der Kindklasse mit demselben Namen wie eine in der Elternklasse ersetzt diese für Instanzen der Kindklasse. Das nennt man Überschreiben. Jede Unterklasse kann denselben Aufruf auf ihre eigene Art beantworten:
Ausgabe:
Rectangle with area 12
Square with area 25
Shape with area 0
summary ist nur einmal definiert, auf Shape, ruft aber self.area() auf. Weil self das tatsächliche Objekt ist, führt ein Rectangle Rectangle.area aus, und ein Square findet area bei seiner Elternklasse Rectangle. Der Code der Elternklasse ruft die Methode der Kindklasse auf, ohne zu wissen, dass es die Kindklasse gibt. Genau das macht Vererbung nützlich: Ein gemeinsamer Ablauf wird geteilt, und jede Unterklasse füllt die Details aus.
Die Elternklasse mit super() aufrufen
Wenn eine Kindklasse ein eigenes __init__ definiert, läuft das __init__ der Elternklasse nicht mehr von selbst. Braucht die Kindklasse trotzdem die Einrichtung der Elternklasse, ruf sie mit super().__init__() auf:
Ausgabe:
Ada 1050.0
super() liefert ein Objekt, das Methoden ab der Elternklasse der aktuellen Klasse sucht. super().__init__(owner, balance) führt deshalb Account.__init__ auf demselben Objekt aus, und danach existieren self.owner und self.balance.
Lässt du diese Zeile weg, ist das Objekt nur halb aufgebaut:
Ausgabe:
0.05
AttributeError: 'SavingsAccount' object has no attribute 'owner'
Du kannst stattdessen Account.__init__(self, owner) schreiben, und älterer Code tut das oft. super() ist vorzuziehen, weil es den Namen der Elternklasse nicht wiederholt und mit Mehrfachvererbung korrekt zusammenarbeitet, die weiter unten behandelt wird.
Eine Methode erweitern statt ersetzen
super() funktioniert in jeder Methode, nicht nur in __init__. Wenn du innerhalb einer überschriebenen Methode die Version der Elternklasse aufrufst, kannst du Verhalten drumherum ergänzen, statt sie neu zu schreiben:
Ausgabe:
started
[12:00] started
INFO [12:00] started
Jede Stufe fügt ein Stück hinzu und reicht den Rest nach oben weiter. Ändert sich format in der Elternklasse später, übernehmen beide Kindklassen die Änderung.
isinstance und issubclass
Eine Instanz einer Unterklasse ist auch eine Instanz jeder Klasse darüber. isinstance(obj, cls) prüft diese Beziehung, und issubclass(child, parent) prüft sie zwischen zwei Klassen:
Nimm lieber isinstance, statt type(obj) direkt zu vergleichen. Eine Prüfung wie type(obj) is Animal lehnt jede Unterklasse ab und geht kaputt, sobald jemand Dog hinzufügt.
Jede Klasse, die du schreibst, erbt von object, auch wenn du es nicht hinschreibst. class Animal: ist dasselbe wie class Animal(object):. Daher kommen Standardmethoden wie __str__, __repr__ und __eq__, und deshalb ändert ein überschriebenes __str__, was print() anzeigt:
Ausgabe:
(2, 3)
True
Unterklassen zwingen, eine Methode zu implementieren
Manchmal soll die Elternklasse nie direkt verwendet werden, sondern nur als Vorlage dienen. Das Modul abc kennzeichnet eine solche Klasse als abstrakt. Jede Methode mit dem Dekorator @abstractmethod muss überschrieben werden, bevor sich die Klasse instanziieren lässt:
Die ersten drei Ausgabezeilen sind der Export:
exporting 2 rows
1,2
3,4
Die letzte Zeile ist ein TypeError, der Exporter und die fehlende Methode export nennt (der genaue Wortlaut unterscheidet sich zwischen Python-Versionen). Der Fehler zeigt sich beim Erzeugen des Objekts, nicht erst später, wenn export schließlich aufgerufen wird.
Mehrfachvererbung und die MRO
Eine Klasse kann mehr als eine Elternklasse angeben. Dann erbt sie von allen:
Ausgabe:
paddling flapping
swims
['Duck', 'Swimmer', 'Flyer', 'object']
Beide Elternklassen definieren move, und Duck bekommt die Version von Swimmer. Python entscheidet das über die Method Resolution Order (MRO): eine einzige Liste von Klassen, gespeichert in Duck.__mro__, die jede Attributsuche von vorn nach hinten abläuft. Sie beginnt mit der Klasse selbst, behält die Elternklassen in der Reihenfolge, in der du sie angegeben hast, stellt jede Klasse vor ihre eigenen Elternklassen und endet mit object. Vertauschst du die Reihenfolge zu class Duck(Flyer, Swimmer), liefert move den Wert "flies".
super() folgt derselben Liste. In einer Methode von Swimmer bedeutet super() „die nächste Klasse nach Swimmer in der MRO der Klasse des Objekts“, und das ist bei einem Duck die Klasse Flyer, nicht object. So können kooperative Klassen jeweils super() aufrufen, und jede Klasse in der Kette läuft genau einmal, selbst in einer Raute, in der zwei Elternklassen eine gemeinsame Großelternklasse haben.
In der Praxis wird Mehrfachvererbung vor allem für Mixins verwendet: kleine Klassen, die eine einzelne Fähigkeit hinzufügen, etwa ein JsonMixin mit einer Methode to_json, kombiniert mit einer Hauptelternklasse. Tiefe Hierarchien mit mehreren vollständigen Elternklassen sind schwer nachzuvollziehen und selten nötig.
Vererbung oder Komposition?
Vererbung bildet eine „ist ein“-Beziehung ab. Ein SavingsAccount ist ein Account: Jeder Code, der mit einem Account arbeitet, sollte unverändert auch damit funktionieren. Wenn dieser Satz falsch klingt, willst du wahrscheinlich Komposition, die „hat ein“ abbildet: Das Objekt hält ein anderes Objekt als Attribut und ruft es auf.
Ausgabe:
Roadster: 300 hp engine running
class Car(Engine) würde auch laufen, würde aber behaupten, ein Auto sei eine Art Motor. Car bekäme jede Motormethode, und ein anderer Motor ließe sich nur mit einer anderen Klasse einbauen. Mit Komposition bleiben die Teile getrennt, und den Motor zu tauschen heißt nur, ein anderes Objekt zu übergeben.
Ein paar Anzeichen, dass Vererbung das falsche Werkzeug ist:
- Die Kindklasse überschreibt die meisten Methoden der Elternklasse oder schaltet einige davon ab.
- Du erbst nur, um eine oder zwei Hilfsmethoden wiederzuverwenden.
- Die Hierarchie wächst über zwei oder drei Ebenen hinaus, und du musst mehrere Dateien lesen, um zu sehen, was eine Methode tut.
Häufige Fehler
super().__init__()vergessen. Das__init__der Kindklasse ersetzt das der Elternklasse, also werden die Attribute der Elternklasse nie gesetzt, und später kommt einAttributeError.selfansuper()-Aufrufe übergeben. Schreibsuper().__init__(name), nichtsuper().__init__(self, name).super()bindet die Methode bereits an das aktuelle Objekt.- Unpassende Signaturen beim Überschreiben. Wenn gemeinsamer Code
speak(self)der Elternklasse aufruft, bricht einspeak(self, volume)in der Kindklasse mit einem zusätzlichen Pflichtargument diesen Code. Halte Überschreibungen kompatibel oder gib neuen Parametern Standardwerte. type(obj) == SomeClassprüfen. Das ignoriert Unterklassen. Nimmisinstance.- Vererbung nutzen, um Code zwischen nicht verwandten Klassen zu teilen. Verschieb den gemeinsamen Code stattdessen in eine Funktion oder ein Hilfsobjekt.
Häufig gestellte Fragen
Wie erbt man in Python von einer Klasse?
Schreib die Elternklasse in Klammern hinter den Namen der neuen Klasse: class Dog(Animal):. Dog bekommt jede Methode und jedes Klassenattribut von Animal und kann neue hinzufügen oder vorhandene überschreiben.
Was macht super().__init__()?
Es ruft das __init__ der Elternklasse auf, damit die Elternklasse ihre eigenen Attribute einrichten kann. Wenn eine Unterklasse ein eigenes __init__ definiert, ruft Python das der Elternklasse nicht automatisch auf. Du rufst also super().__init__(...) mit den Argumenten auf, die die Elternklasse braucht, und setzt danach die zusätzlichen Attribute der Unterklasse.
Was ist der Unterschied zwischen isinstance und type in Python?
isinstance(obj, Animal) ist True für ein Animal und für Instanzen jeder Unterklasse von Animal. type(obj) is Animal ist nur True, wenn das Objekt direkt aus Animal erzeugt wurde. Nimm lieber isinstance, weil es weiter funktioniert, wenn jemand eine Unterklasse hinzufügt.
Unterstützt Python Mehrfachvererbung?
Ja: class C(A, B): erbt von beiden. Wenn beide Elternklassen dieselbe Methode definieren, wählt Python eine anhand der Method Resolution Order (MRO), die du mit C.__mro__ ansehen kannst. Sie nennt zuerst C, dann die Elternklassen von links nach rechts und zuletzt object.
Wann sollte ich Komposition statt Vererbung verwenden?
Nimm Vererbung für eine „ist ein“-Beziehung, bei der die Unterklasse überall einsetzbar ist, wo die Elternklasse erwartet wird (ein SavingsAccount ist ein Account). Nimm Komposition, also ein Objekt als Attribut speichern, für eine „hat ein“-Beziehung (ein Car hat eine Engine). Im Zweifel lässt sich Komposition später leichter ändern.