von: Konstantin
aktualisiert: Sept. 13, 2024
Kennst du dich mit Vererbung in Python aus, so spart dir dies viel Zeit und Mühe und deshalb zeige ich dir in diesem Beitrag was zur Vererbung wissen musst!
Zunächst einmal das Wichtigste: Kennst du dich mit Vererbung aus, so kann dir dies Zeit und Arbeit sparen. Deshalb sollest du dich damit befassen. Um die Vorteile zu sehen, hier zu nächst ein einfaches Beispiel. In diesem Beispiel erstellen wir zunächst zwei unabhängige Klassen von Tieren, eine für Kuh und eine für Schwein. Beide haben eigene Eigenschaften und Methoden, die nicht voneinander abhängen.
Ohne Vererbung:
# Klasse Kuh
class Kuh:
def __init__(self, name, alter, farbe):
self.name = name
self.alter = alter
self.farbe = farbe
def beschreibung(self):
return f"{self.name} ist eine {self.farbe} Kuh und ist {self.alter} Jahre alt."
# Klasse Schwein
class Schwein:
def __init__(self, name, alter, farbe):
self.name = name
self.alter = alter
self.farbe = farbe
def beschreibung(self):
return f"{self.name} ist ein {self.farbe} Schwein und ist {self.alter} Jahre alt."
# Objekte erstellen und Methoden aufrufen
kuh = Kuh("Bella", 5, "braun")
schwein = Schwein("Ferdinand", 7, "rosa")
print(kuh.beschreibung())
# Ausgabe: Bella ist eine braun Kuh und ist 5 Jahre alt.
print(schwein.beschreibung())
# Ausgabe: Ferdinand ist ein rosa Schwein und ist 7 Jahre alt.
Hier haben die Klassen Kuh und Schwein ähnliche, aber eigenständige Methoden. Sie teilen keinen Code, obwohl sie ähnliche Eigenschaften haben. Die ähnlichen Eigenschaften wie name
, alter
, und farbe
brauchst du aber nicht doppelt zu definieren und kannst einfache eine Basisklasse Tier erstellen, von der die Eigenschaften und Methoden geerbt werden können. Schau dir das nachfolgende Beispiel an:
# Basis-Klasse Tier
class Tier:
def __init__(self, name, alter, farbe):
self.name = name
self.alter = alter
self.farbe = farbe
def beschreibung(self):
return f"{self.name} ist ein {self.farbe} Tier und ist {self.alter} Jahre alt."
# Klasse Kuh erbt von Tier
class Kuh(Tier):
def __init__(self, name, alter, farbe):
Tier.__init__(self, name, alter, farbe)
def beschreibung(self):
return f"{self.name} ist eine {self.farbe} Kuh und ist {self.alter} Jahre alt."
# Klasse Schwein erbt von Tier
class Schwein(Tier):
def __init__(self, name, alter, farbe):
Tier.__init__(self, name, alter, farbe)
def beschreibung(self):
return f"{self.name} ist ein {self.farbe} Schwein und ist {self.alter} Jahre alt."
# Objekte erstellen und Methoden aufrufen
kuh = Kuh("Bella", 5, "braun")
schwein = Schwein("Ferdinand", 7, "rosa")
print(kuh.beschreibung())
# Ausgabe: Bella ist eine braune Kuh und ist 5 Jahre alt.
print(schwein.beschreibung())
# Ausgabe: Ferdinand ist ein rosa Schwein und ist 7 Jahre alt.
Klar, auf den ersten Blick sieht das Ganze jetzt nicht viel einfacher und kürzer aus. Wenn du aber mit Klassen arbeitest, die dutzende Methoden haben oder mit Klassen, die jemand anderes z.B. bei GitHub zur Verfügung stellt und du nur ein oder zwei Methoden zu dieser Klasse hinzufügen willst, dann wird Vererbung nahezu unverzichtbar!
Aber hier noch ein paar Erläuterungen zum Code:
Tier
enthält die gemeinsamen Attribute name
, alter
, und farbe
sowie die Methode beschreibung()
, die diese Attribute ausgibt.Kuh
und Schwein
erben von der Klasse Tier
. Sie rufen den Konstruktor der Basisklasse mit Tier.__init__(self, name, alter, farbe)
auf, um die allgemeinen Attribute zu setzen.beschreibung()
ist für Kuh und Schwein jeweils angepasst!Durch Vererbung vermeidest du doppelten Code und kannst und eine gemeinsame Struktur für ähnliche Klassen wie Kuh und Schwein schaffen.
Die Methode super()
ist die gängige und elegantere Methode, um Methoden der Basisklasse aufzurufen. Schauen wir uns das Beispiel von eben nochmals an. Hier wird die Methode super()
verwendet:
# Basis-Klasse Tier
class Tier:
def __init__(self, name, alter, farbe):
self.name = name
self.alter = alter
self.farbe = farbe
def beschreibung(self):
return f"{self.name} ist ein {self.farbe} Tier und ist {self.alter} Jahre alt."
# Klasse Kuh erbt von Tier
class Kuh(Tier):
def __init__(self, name, alter, farbe):
super().__init__(name, alter, farbe) # Aufruf des Konstruktors der Elternklasse mit super()
def beschreibung(self):
return f"{self.name} ist eine {self.farbe} Kuh und ist {self.alter} Jahre alt."
# Klasse Schwein erbt von Tier
class Schwein(Tier):
def __init__(self, name, alter, farbe):
super().__init__(name, alter, farbe) # Aufruf des Konstruktors der Elternklasse mit super()
def beschreibung(self):
return f"{self.name} ist ein {self.farbe} Schwein und ist {self.alter} Jahre alt."
# Objekte erstellen und Methoden aufrufen
kuh = Kuh("Bella", 5, "braun")
schwein = Schwein("Ferdinand", 7, "rosa")
print(kuh.beschreibung())
# Ausgabe: Bella ist eine braune Kuh und ist 5 Jahre alt.
print(schwein.beschreibung())
# Ausgabe: Ferdinand ist ein rosa Schwein und ist 7 Jahre alt.
Erläuterung des Codes:
Der Aufruf von super().__init__(name, alter, farbe)
in den Klassen Kuh und Schwein sorgt dafür, dass der Konstruktor der Basisklasse Tier
ausgeführt wird. Im Beispiel ohne super()
lautete die entsprechende Code-Zeile: Tier.__init__(self, name, alter, farbe)
.
Vorteile der Methode super()
:
super()
kannst du Methoden der Basisklasse verwenden ohne direkt auf die Elternklasse zugreifen zu müssen.super()
ist dein Code einfacher zu warten und flexibler.super()
kannst du einfacher Methoden der Basisklasse aufrufen, um sie zu erweitern.Zusammengefasst: wenn es keinen wirklich guten Grund gibt (und den gibt es eigentlich nicht), verwende die Methode super()
und vermeide es direkt auf Methoden der Basisklasse zuzugreifen
Oft kommt es vor, dass du eine Klasse um eine Methode erweitern willst. Hier verwendest du ebenfalls super().
class Mitarbeiter:
def __init__(self, name, gehalt):
self.name = name
self.gehalt = gehalt
def details(self):
return f"{self.name} verdient {self.gehalt} Euro im Monat."
class Manager(Mitarbeiter):
def __init__(self, name, gehalt, bonus):
super().__init__(name, gehalt)
self.bonus = bonus
def details(self):
return super().details() + f" Bonus: {self.bonus} Euro."
chef = Manager("Anna", 5000, 1000)
print(chef.details())
# Ausgabe: Anna verdient 5000 Euro im Monat. Bonus: 1000 Euro.
Erläuterung zum Code:
Du möchtest die grundlegenden Informationen (Name und Gehalt) behalten, aber zusätzliche Daten (Bonus) hinzufügen. Das erreichst du durch das Aufrufen von super()
innerhalb der Methode details()
.
Mittels Mehrfachvererbung kannst du Eigenschaften von verschiedenen Basisklassen in eine neue Klasse übernehmen! Schauen wir uns nochmal das Beispiel mit den Tieren an. Wir definieren zusätzliche Basisklassen, die verschiedene Eigenschaften repräsentieren:
# Klasse Tier
class Tier:
def __init__(self, name):
self.name = name
def bewegen(self):
return f"{self.name} bewegt sich."
# Klasse Fliegen
class Fliegen:
def fliegen(self):
return f"{self.name} fliegt durch die Luft."
# Klasse Schwimmen
class Schwimmen:
def schwimmen(self):
return f"{self.name} schwimmt im Wasser."
# Klasse Ente erbt von Tier, Fliegen und Schwimmen
class Ente(Tier, Fliegen, Schwimmen):
def __init__(self, name):
Tier.__init__(self, name) # Konstruktor von Tier aufrufen
# Objekte erstellen und Methoden aufrufen
ente = Ente("Daffy")
print(ente.bewegen()) # Ausgabe: Daffy bewegt sich.
print(ente.fliegen()) # Ausgabe: Daffy fliegt durch die Luft.
print(ente.schwimmen()) # Ausgabe: Daffy schwimmt im Wasser.
# Ausgabe der MRO:
print(Ente.__mro__)
help(Ente)
Erläuterung zum Code:
Ente
erbt von drei Klassen: Tier
, Fliegen
, und Schwimmen
.Ente
Methoden von allen Elternklassen (bewegen()
von Tier
, fliegen()
von Fliegen
und schwimmen()
von Schwimmen
) verwenden.__mro__
oder der Funktion help(Ente)
einsehen.Im vorherigen Beispiel hat eine abgeleitete Klasse von mehreren Basisklassen unterschiedlich benannte Methoden geerbt. Sind die Methoden jedoch gleich benannt, so kommt es zu einem Namenskonflikt. Hier ist die MRO (Method Resolution Order) entscheidend. Die Basisklasse, die du beim "Erben" zuerst verwendest, bekommt die höhere Gewichtung. Schau dir dafür das folgende Beispiel an:
class Fliegt:
def bewegen(self):
print("Ich fliege")
class Laeuft:
def bewegen(self):
print("Ich laufe")
class Schwimmt:
def bewegen(self):
print("Ich schwimme")
#--------------------------------------------
class FlugEnte(Fliegt, Laeuft):
pass
ente = FlugEnte()
ente.bewegen() #Ausgabe: Ich fliege
#--------------------------------------------
class LaufEnte(Laeuft, Fliegt):
pass
ente = LaufEnte()
ente.bewegen() #Ausgabe: Ich laufe
#--------------------------------------------
class SchwimmEnte(Schwimmt, Laeuft, Fliegt):
pass
ente = SchwimmEnte()
ente.bewegen()
Erläuterung zum Code:
bewegen()
der verschiedenen Basisklassen immer gleich benannt. Dadurch käme es bei Mehrfachvererbungen zu einem Namenskonflikt. Dank MRO ist aber klar, welche Klasse Priorität hat.Vererbung ist mächtig, aber nicht immer die beste Lösung. Manchmal ist Komposition besser, wenn eine "hat-ein"-Beziehung sinnvoller ist als eine "ist-ein"-Beziehung. Schau dir das Beispiel unten an: Anstatt ein Auto von einem Motor erben zu lassen (was keinen Sinn ergibt), kannst du sagen: Ein Auto hat einen Motor.
class Motor:
def starten(self):
return "Motor startet."
class Auto:
def __init__(self):
self.motor = Motor()
def fahren(self):
return f"{self.motor.starten()} Auto fährt los."
auto = Auto()
print(auto.fahren())
# Ausgabe: Motor startet. Auto fährt los.
Hier besitzt das Auto einen Motor, anstatt von ihm zu erben. In der Praxis ist Komposition oft flexibler, weil du die Beziehungen zwischen den Klassen klarer ausdrückst.
Fazit:
Pferd
ist ein Tier
.Auto
hat einen Motor
.Python betrachtet Attribute, die mit zwei Unterstrichen (__
) beginnen, als private Attribute. Das bedeutet, dass du außerhalb der Klasse nicht direkt auf sie zugreifen kannst. Dies gilt auch für abgeleitete Klassen. Python setzt dafür das sogenannte Name Mangling ein, um den Namen des Attributs zu verändern und so den direkten Zugriff zu verhindern.
class Tier:
def __init__(self, name, alter):
self.__name = name # Privates Attribut
self.alter = alter
def beschreibung(self):
return f"{self.__name} ist {self.alter} Jahre alt."
class Hund(Tier):
def bellen(self):
return f"{self.__name} bellt!" # Führt zu einem Fehler
# Objekt erstellen
hund = Hund("Rex", 5)
print(hund.beschreibung()) # Ausgabe: Rex ist 5 Jahre alt.
print(hund.bellen()) # Fehler: 'Hund' object has no attribute '__name'
Erläuterung zum Code:
__name
ist in der Klasse Tier
als privat definiert und somit für die Klasse Hund
nicht direkt zugänglich. Python verändert intern den Namen (Name Mangling), sodass es nicht mit einem einfachen __name
aufgerufen werden kann.Hier nochmal der gleiche Code mit eine Methode, die auf __name
zugreift:
class Tier:
def __init__(self, name, alter):
self.__name = name
self.alter = alter
def get_name(self):
return self.__name
class Hund(Tier):
def bellen(self):
return f"{self.get_name()} bellt!"
# Objekt erstellen und Methoden aufrufen
hund = Hund("Rex", 5)
print(hund.bellen()) # Ausgabe: Rex bellt!
Erläuterung zum Code:
Hier wurde in der Basisklasse die Methode get_name(self)
hinzugefügt. Diese gibt den Namen zurück. Über diese Methode kannst du somit über Umwege auf das Attribut __name
der Basisklasse zugreifen.
Kurz zusammengefasst:
In Python werden private Attribute durch das Namensschema mit doppelten Unterstrichen geschützt, aber du kannst sie indirekt über Methoden in der Basisklasse zugänglich machen.
Die Funktionen isinstance()
und issubclass()
sind nützlich, wenn du überprüfen möchtest, ob ein Objekt von einer bestimmten Klasse stammt oder ob eine Klasse von einer anderen abgeleitet ist:
isinstance()
: Diese Funktion prüft, ob ein Objekt einer bestimmten Klasse oder einer Klasse aus einer Vererbungshierarchie angehört.issubclass()
: Diese Funktion prüft, ob eine Klasse von einer anderen Klasse abgeleitet ist.class Tier:
pass
class Hund(Tier):
pass
rex = Hund()
# Prüft, ob rex ein Objekt der Klasse Hund oder einer abgeleiteten Klasse ist
print(isinstance(rex, Hund)) # Ausgabe: True
# Prüft, ob rex ein Objekt der Klasse Tier ist
print(isinstance(rex, Tier)) # Ausgabe: True
# Prüft, ob rex ein Objekt einer anderen Klasse (z. B. str) ist
print(isinstance(rex, str)) # Ausgabe: False
Erläuterung zum Code:isinstance(rex, Hund)
gibt True
zurück, weil rex
eine Instanz der Klasse Hund
ist. isinstance(rex, Tier)
gibt auch True
zurück, da Hund
von Tier
erbt.
class Tier:
pass
class Hund(Tier):
pass
class Katze:
pass
# Prüft, ob Hund eine Unterklasse von Tier ist
print(issubclass(Hund, Tier)) # Ausgabe: True
# Prüft, ob Katze eine Unterklasse von Tier ist
print(issubclass(Katze, Tier)) # Ausgabe: False
# Prüft, ob Tier eine Unterklasse von object ist (alle Klassen erben von object)
print(issubclass(Tier, object)) # Ausgabe: True
Erläuterung zum Code:issubclass(Hund, Tier)
gibt True
zurück, weil Hund
von Tier
erbt. issubclass(Katze, Tier)
gibt False
zurück, weil Katze
keine Unterklasse von Tier
ist.