So funktioniert Vererbung in Python

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!

1. Warum ist Vererbung wichtig: ein Beispiel

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:

  • Die Klasse Tier enthält die gemeinsamen Attribute name, alter, und farbe sowie die Methode beschreibung(), die diese Attribute ausgibt.
  • Die Klassen 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.
  • Die Methode 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.

2. Die unverzichtbare Methode super()

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():

  • Mit super() kannst du Methoden der Basisklasse verwenden ohne direkt auf die Elternklasse zugreifen zu müssen.
  • Mit super() ist dein Code einfacher zu warten und flexibler.
  • Du musst die korrekte Basisklasse nur einmal in der abgeleiteten Klasse benennen.
  • Mit 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

3. So erweiterst du Methoden

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().

4. Mehrfachvererbung und MRO (Method Resolution Order)

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:

  • Klasse Ente erbt von drei Klassen: Tier, Fliegen, und Schwimmen.
  • Durch die Mehrfachvererbung kann die Klasse Ente Methoden von allen Elternklassen (bewegen() von Tier, fliegen() von Fliegen und schwimmen() von Schwimmen) verwenden.
  • MRO (Method Resolution Order): Python sucht in einer klar definierten Reihenfolge (MRO), von welcher Elternklasse eine Methode aufgerufen wird, falls mehrere Elternklassen die gleiche Methode hätten. Diese Reihenfolge kannst du mit __mro__ oder der Funktion help(Ente) einsehen.

5. So löst Python Namenskonflikte bei Mehrfachvererbung

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:

  • Die Methode 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.

6. Vererbung versus Komposition

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:

  • Vererbung nutzt eine "ist-ein"-Beziehung: Ein Pferd ist ein Tier.
  • Komposition nutzt eine "hat-ein"-Beziehung: Ein Auto hat einen Motor.

7. Private Attribute

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:

  • Das Attribut __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.
  • Lösung: Verwende in abgeleiteten Klassen Methoden, um auf die private Attribute der Basisklasse zuzugreifen.

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.

8. isinstance() und issubclass()

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.

 

 

Teilen: