- 1 Section
- 10 Lessons
- unbegrenzt
- Python Fortgeschritten10
- 1.1Module und Pakete: import, pip, venv
- 1.2Objektorientierung in Python: Klassen, __init__, self
- 1.3Vererbung und Mehrfachvererbung in Python
- 1.4Decorators und Generatoren
- 1.5Fehlerbehandlung vertieft: eigene Exceptions
- 1.6Reguläre Ausdrücke mit re
- 1.7Arbeiten mit APIs: requests, JSON
- 1.8Automatisierung mit os, subprocess, pathlib
- 1.9Unit-Tests in Python: pytest
- 1.10Praxisprojekt: Automatisierungsskript mit Tests
Vererbung und Mehrfachvererbung in Python
In Objektorientierung in Python hast du Klassen, __init__ und self kennengelernt. Vererbung ist der nächste große Schritt: eine Klasse erbt Eigenschaften und Verhalten einer anderen, fügt eigenes hinzu und kann Bestehendes überschreiben. So entstehen Klassen-Hierarchien, in denen sich Code wiederverwenden lässt.
Python erlaubt – anders als Java – auch Mehrfachvererbung: eine Klasse kann von mehreren Eltern erben. Damit das funktioniert, braucht es ein klares Regelwerk dafür, welche Methode wann gerufen wird: die Method Resolution Order (MRO). Außerdem siehst du in dieser Lektion abstrakte Basisklassen, Mixins und das Python-typische „Duck Typing".
1) Die einfachste Vererbung
In Python erbt eine Klasse, indem sie die Eltern-Klasse in Klammern schreibt:
class Tier: def __init__(self, name): self.name = name def stimme(self): return "..." class Hund(Tier): # erbt von Tier def stimme(self): # überschreibt Methode return "Wuff!" h = Hund("Rex") print(h.name) # Rex – von Tier geerbt print(h.stimme()) # Wuff! – überschrieben
Die Tochterklasse Hund erbt automatisch __init__ und alle Methoden von Tier. Was sie selbst definiert, gilt zusätzlich oder überschreibt das Geerbte. Anders als Java braucht es kein Keyword wie extends – nur die Klammern.
2) super() – auf die Eltern-Methode zugreifen
Wenn die Tochterklasse einen eigenen Konstruktor schreibt, will sie meist trotzdem das tun, was die Eltern-Klasse beim __init__ macht. Dafür gibt es super():
class Tier: def __init__(self, name): self.name = name class Hund(Tier): def __init__(self, name, rasse): super().__init__(name) # ruft Tier.__init__ self.rasse = rasse # eigene Erweiterung h = Hund("Rex", "Labrador") print(h.name, h.rasse) # Rex Labrador
super() liefert ein Objekt, über das du auf die Eltern-Klasse zugreifst. Beim Konstruktor üblich: zuerst super().__init__(...), dann eigene Felder setzen. Vergisst man super().__init__(), sind die geerbten Attribute schlicht nicht initialisiert – häufiger Fehler.
super() funktioniert auch in anderen Methoden, wenn du das Eltern-Verhalten erweitern (statt nur ersetzen) willst:
class Tier: def beschreibung(self): return f"Tier namens {self.name}" class Hund(Tier): def beschreibung(self): basis = super().beschreibung() return f"{basis} (Hund)"
3) Methoden überschreiben
Eine Methode aus der Eltern-Klasse wird einfach durch eine gleichnamige Methode in der Tochter ersetzt. Es gibt kein @Override wie in Java – Python prüft nicht, ob du wirklich eine Eltern-Methode überschreibst (Tippfehler bleiben unbemerkt).
class Form: def flaeche(self): return 0 class Quadrat(Form): def __init__(self, seite): self.seite = seite def flaeche(self): # überschreibt Form.flaeche return self.seite ** 2 class Kreis(Form): def __init__(self, radius): self.radius = radius def flaeche(self): return 3.14159 * self.radius ** 2 for form in [Quadrat(3), Kreis(2)]: print(form.flaeche()) # 9, dann 12.56636
Das ist Polymorphismus: derselbe Methodenaufruf form.flaeche() verhält sich je nach konkreter Klasse anders. Die Schleife muss die Typen nicht kennen – Python ruft automatisch die richtige Methode.
4) isinstance und issubclass
Um zur Laufzeit zu prüfen, ob ein Objekt von einer Klasse ist:
h = Hund("Rex", "Labrador") isinstance(h, Hund) # True isinstance(h, Tier) # True – Hund erbt von Tier isinstance(h, str) # False isinstance(h, (Hund, Katze)) # True wenn eines von beiden issubclass(Hund, Tier) # True – auf Klassen-Ebene
isinstance() prüft, ob ein Objekt zur Klasse oder einer ihrer Eltern gehört. issubclass() prüft das auf Klassen-Ebene (ohne konkrete Instanz). Beide sind nützlich für Typprüfungen – aber sparsam einsetzen, denn häufig ist „Duck Typing" der bessere Python-Stil (Abschnitt 11).
5) Mehrfachvererbung
Anders als Java erlaubt Python Mehrfachvererbung. Mehrere Eltern-Klassen kommen durch Komma getrennt in die Klammer:
class Schwimmer: def schwimmen(self): return "schwimmt" class Flieger: def fliegen(self): return "fliegt" class Ente(Schwimmer, Flieger): # beides pass e = Ente() print(e.schwimmen()) # schwimmt print(e.fliegen()) # fliegt
Eine Ente erbt beides – Methoden von Schwimmer und von Flieger. Das ist mächtig, aber auch verzwickt: was, wenn beide Eltern eine Methode mit demselben Namen haben? Dafür gibt es die MRO.
6) Method Resolution Order (MRO)
Wenn Python in einer Klassen-Hierarchie eine Methode oder Attribut sucht, geht es eine bestimmte Reihenfolge durch: die Method Resolution Order. Bei einfacher Vererbung ist das trivial (Kind → Eltern → object), bei Mehrfachvererbung wird es interessant.
Welpe → Hund → Tier → object. Python sucht eine Methode in dieser Reihenfolge und nimmt die erste, die passt.Du kannst die MRO einer Klasse jederzeit abfragen:
print(Welpe.__mro__) # (Welpe, Hund, Tier, object) print(Welpe.mro()) # identisch als Liste
7) C3-Linearisierung bei Mehrfachvererbung
Bei Mehrfachvererbung wird die MRO mit einem Algorithmus namens C3-Linearisierung berechnet. Klingt kompliziert, ist aber im Kern: gehe von links nach rechts durch die Eltern, beachte aber, dass jede Klasse vor ihren eigenen Eltern stehen muss.
class A: pass class B(A): pass class C(A): pass class D(B, C): pass print(D.__mro__) # (D, B, C, A, object)
Suche eine Methode auf einem D-Objekt: zuerst in D, dann B, dann C, dann A, dann object. Beachte: A kommt nicht direkt nach B, weil C auch von A erbt – also muss A erst nach beiden Kindern stehen.
8) Das Diamond-Problem
Das berühmte „Diamond-Problem" entsteht, wenn die Hierarchie eine Raute bildet – eine gemeinsame Großeltern-Klasse, zwei Eltern, eine Tochter. Welche Methode wird genommen?
A taucht in der MRO nur einmal auf, und zwar nach B und C. Damit wird die Methode in A nicht mehrfach gerufen, selbst wenn beide Eltern super() verwenden.9) super() in Mehrfachvererbung
In einer Diamond-Hierarchie wirkt super() magisch: es ruft nicht einfach „die Eltern-Klasse", sondern „die nächste Klasse in der MRO". Damit kann die Hierarchie kooperativ super() aufrufen und jede Methode wird genau einmal ausgeführt:
class A: def init(self): print("A") class B(A): def init(self): print("B") super().init() class C(A): def init(self): print("C") super().init() class D(B, C): def init(self): print("D") super().init() D().init() # Ausgabe: D B C A
In B ruft super() nicht direkt A, sondern die nächste Klasse in der MRO von D – das ist C. So wird A ganz am Ende genau einmal ausgeführt. Diese „kooperative Mehrfachvererbung" funktioniert nur, wenn alle Klassen brav super() aufrufen.
10) Abstrakte Basisklassen (ABC)
Manchmal willst du eine Klasse haben, die als „Vorlage" dient – sie soll nicht direkt instanziiert werden, sondern Tochterklassen müssen bestimmte Methoden implementieren. In Java heißt das abstract class; in Python erreichst du es mit dem Modul abc:
from abc import ABC, abstractmethod class Form(ABC): @abstractmethod def flaeche(self): pass def info(self): return f"Fläche: {self.flaeche()}" class Quadrat(Form): def __init__(self, seite): self.seite = seite def flaeche(self): return self.seite ** 2 f = Form() # TypeError – Form ist abstrakt q = Quadrat(3) # OK – flaeche ist implementiert
Eine Klasse, die von ABC erbt und mindestens eine Methode mit @abstractmethod markiert, kann nicht direkt instanziiert werden. Tochterklassen müssen alle abstrakten Methoden implementieren – sonst kommt beim Erzeugen ein TypeError. Das ist Pythons Pendant zu Interfaces und abstrakten Klassen.
11) Duck Typing – Python ohne Interfaces
Python erlaubt auch einen Stil ganz ohne Vererbung: Duck Typing. Der Slogan: „Wenn es wie eine Ente aussieht und wie eine Ente quakt, ist es eine Ente." Übersetzt: es zählt nicht, was eine Klasse erbt – es zählt, welche Methoden sie hat.
class Hund: def stimme(self): return "Wuff!" class Katze: def stimme(self): return "Miau!" def alle_rufen(tiere): for t in tiere: print(t.stimme()) # funktioniert für ALLES mit stimme() alle_rufen([Hund(), Katze()])
Hund und Katze erben hier nichts. Trotzdem funktioniert alle_rufen – weil beide eine Methode stimme() haben. In statisch typisierten Sprachen müsstest du ein gemeinsames Interface definieren; in Python reicht „macht das Richtige". Das ist sehr flexibel, aber Fehler tauchen erst zur Laufzeit auf.
12) Mixins – Funktionalität dazu-mischen
Ein Mixin ist eine kleine Klasse, die nicht alleine instanziiert wird, sondern Mehrfachvererbung nutzt, um anderen Klassen ein Stück Funktionalität hinzuzufügen:
class JSONExportMixin: def to_json(self): import json return json.dumps(self.__dict__) class LogMixin: def log(self, msg): print(f"[{type(self).__name__}] {msg}") class Kunde(JSONExportMixin, LogMixin): def __init__(self, name, email): self.name = name self.email = email k = Kunde("Anna", "a@b.de") print(k.to_json()) # aus JSONExportMixin k.log("erstellt") # aus LogMixin
Hund ist ein Tier.@abstractmethod implementieren.Konvention: Mixin-Klassen tragen oft den Suffix Mixin im Namen. Sie ergänzen Klassen um Querschnitts-Funktionen wie Logging, Serialisierung oder Caching, ohne dass eine echte „ist-ein"-Beziehung dahintersteht.
13) Komposition vs. Vererbung
Eine alte Programmier-Weisheit lautet: „Favor composition over inheritance". Wenn nicht klar ist, dass eine „ist-ein"-Beziehung vorliegt, ist Komposition oft besser:
# Vererbung – nicht ideal, ein Auto IST kein Motor class Auto(Motor): pass # Komposition – sauberer, ein Auto HAT einen Motor class Auto: def __init__(self): self.motor = Motor()
Vererbung erzwingt eine sehr enge Bindung. Komposition lässt sich später leichter ändern – etwa den Motor durch einen Elektromotor ersetzen. Vererbung dort, wo wirklich eine „ist-ein"-Hierarchie besteht; Komposition für „hat-ein"-Beziehungen.
14) Häufige Fehler
- super().__init__() vergessen: die Eltern-Attribute werden nicht gesetzt, später
AttributeErrorbei Zugriff - Methode falsch geschrieben beim Überschreiben:
def Stimme(self):stattdef stimme(self):– Python merkt nichts, die alte Methode bleibt aktiv - Mehrfachvererbung ohne Plan: führt schnell zu „MRO conflict" – Python wirft
TypeError: Cannot create a consistent method resolution order - abstrakte Klasse instanziiert:
TypeError: Can't instantiate abstract class– das ist kein Bug, sondern die ABC-Schutzfunktion - isinstance gegen mehrere Typen mit Liste:
isinstance(x, [int, str])ist falsch – muss Tupel sein:isinstance(x, (int, str)) - Vererbung wo Komposition besser wäre: aufgeblähte Hierarchien, weil man „eine Klasse braucht, die ein bisschen wie X ist"
Zusammenfassung
Vererbung in Python schreibt man mit Klammern hinter dem Klassennamen: class B(A):. Mit super().__init__() ruft die Tochter die Eltern-Initialisierung. Methoden werden überschrieben, indem man eine gleichnamige in der Tochter definiert. Python erlaubt Mehrfachvererbung; die Reihenfolge der Methoden-Suche heißt Method Resolution Order und folgt der C3-Linearisierung. Das Diamond-Problem löst Python elegant – die gemeinsame Basis taucht in der MRO nur einmal auf. Abstrakte Basisklassen mit ABC und @abstractmethod definieren Pflicht-Schnittstellen. Mixins sind kleine Klassen für Querschnitts-Funktionen. Duck Typing erlaubt Polymorphismus ganz ohne Vererbung. Bei Unsicherheit: Komposition statt Vererbung wählen.
