Python-Vererbung
Python-Vererbung verstehen: Unterklassen, Methodenüberschreibung, super(), Mehrfachvererbung, MRO und isinstance – mit ausführbaren Beispielen.
Python-Vererbung verstehen
Vererbung ermöglicht es Ihnen, eine neue Klasse auf Basis einer bestehenden aufzubauen. Die neue Klasse (die Unterklasse oder Kindklasse) erhält automatisch alle Attribute und Methoden der bestehenden Klasse (der Oberklasse, Basisklasse oder Elternklasse). Sie können dann zusätzliches Verhalten hinzufügen oder das Geerbte selektiv überschreiben.
Dieses Kapitel behandelt:
- Die Syntax zur Erstellung von Unterklassen
- Wie
super()funktioniert und warum Sie es verwenden sollten - Methodenüberschreibung und der Aufruf der Elternversion
- Einfache, mehrstufige und Mehrfachvererbung
- Pythons Method Resolution Order (MRO)
isinstance()undissubclass()für Laufzeit-Typprüfungen- Häufige Fallstricke
Bevor Sie dieses Kapitel lesen, stellen Sie sicher, dass Sie mit Python-Klassen und -Objekten vertraut sind. Weitere OOP-Konzepte finden Sie unter Python Encapsulation und Python Abstract Classes.
Die Syntax der Vererbung
Um eine Unterklasse zu erstellen, schreiben Sie den Namen der Elternklasse in Klammern hinter den neuen Klassennamen:
class ParentClass:
pass
class ChildClass(ParentClass):
passEin minimales, aber konkretes Beispiel mit einer Vehicle-Basisklasse und einer Car-Unterklasse:
class Vehicle:
def __init__(self, make, model, year):
self.make = make
self.model = model
self.year = year
def start(self):
print(f"{self.make} {self.model} started.")
def stop(self):
print(f"{self.make} {self.model} stopped.")
class Car(Vehicle):
def __init__(self, make, model, year, num_doors=4):
super().__init__(make, model, year) # delegate to Vehicle.__init__
self.num_doors = num_doors
camry = Car("Toyota", "Camry", 2023)
camry.start() # Toyota Camry started.
camry.stop() # Toyota Camry stopped.
print(camry.num_doors) # 4Car erbt start() und stop() von Vehicle, ohne sie zu duplizieren. Das Hinzufügen von num_doors ist die einzige neue Aufgabe, die Car übernehmen muss.
super() verwenden
super() gibt ein Proxy-Objekt zurück, das Methodenaufrufe an die Elternklasse delegiert. Am häufigsten wird es innerhalb von __init__ verwendet, um die Attribute der Elternklasse zu initialisieren, bevor die eigenen Attribute der Kindklasse hinzugefügt werden:
class Car(Vehicle):
def __init__(self, make, model, year, num_doors=4):
super().__init__(make, model, year) # runs Vehicle.__init__
self.num_doors = num_doorsWarum ist super() dem direkten Aufruf Vehicle.__init__(self, ...) vorzuziehen?
- Wartbarkeit: Wenn Sie die Elternklasse umbenennen oder ersetzen, müssen Sie nur eine Zeile ändern.
- Mehrfachvererbung:
super()folgt Pythons Method Resolution Order (MRO), sodass jede Klasse in der Kette korrekt aufgerufen wird. Das direkte Kodieren des Elternnamens überspringt diese Logik.
Sie können super() für jede Methode aufrufen, nicht nur für __init__:
class Car(Vehicle):
def start(self):
super().start() # call Vehicle.start first
print(f"({self.num_doors}-door model ready)")Methodenüberschreibung
Eine Unterklasse überschreibt eine Elternmethode, indem sie eine Methode mit demselben Namen definiert. Python ruft immer zuerst die am stärksten abgeleitete Version auf:
class Vehicle:
def __init__(self, make, model, year):
self.make = make
self.model = model
self.year = year
def start(self):
print(f"{self.make} {self.model} started.")
def stop(self):
print(f"{self.make} {self.model} stopped.")
class Car(Vehicle):
def start(self):
print(f"{self.make} {self.model} revved the engine and started.")
camry = Car("Toyota", "Camry", 2023)
camry.start() # Toyota Camry revved the engine and started.
camry.stop() # Toyota Camry stopped. (inherited, not overridden)Wenn Sie das Verhalten der Elternklasse erweitern statt es zu ersetzen möchten, rufen Sie super() innerhalb der Überschreibung auf:
class Car(Vehicle):
def start(self):
super().start() # Vehicle.start runs first
print("Seatbelt reminder: buckle up!") # then add the extra stepVererbung von Klassenattributen
Vererbung gilt auch für Klassenattribute (die instanzübergreifend geteilt werden). Eine Unterklasse kann sie auf dieselbe Weise überschreiben wie Methoden:
class Vehicle:
wheels = 4
class Motorcycle(Vehicle):
wheels = 2 # override the class attribute
class Car(Vehicle):
pass # inherits wheels = 4
print(Motorcycle.wheels) # 2
print(Car.wheels) # 4
print(Vehicle.wheels) # 4Arten der Vererbung
Einfachvererbung
Ein Kind, ein Elternteil — das häufigste Muster.
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return f"{self.name} makes a sound."
class Dog(Animal):
def speak(self):
return f"{self.name} says woof!"
dog = Dog("Rex")
print(dog.speak()) # Rex says woof!Mehrstufige Vererbung
Eine Unterklasse kann selbst wieder als Elternklasse dienen und so eine Kette bilden:
class Vehicle:
def __init__(self, make, model, year):
self.make = make
self.model = model
self.year = year
def stop(self):
print(f"{self.make} {self.model} stopped.")
class Car(Vehicle):
def __init__(self, make, model, year, num_doors=4):
super().__init__(make, model, year)
self.num_doors = num_doors
def start(self):
print(f"{self.make} {self.model} started.")
class ElectricCar(Car):
def __init__(self, make, model, year, range_km):
super().__init__(make, model, year) # calls Car.__init__
self.range_km = range_km
def start(self):
print(f"{self.make} {self.model} powered up silently.")
def charge(self):
print(f"Charging... range is {self.range_km} km.")
tesla = ElectricCar("Tesla", "Model 3", 2024, 580)
tesla.start() # Tesla Model 3 powered up silently.
tesla.charge() # Charging... range is 580 km.
tesla.stop() # Tesla Model 3 stopped. (inherited from Vehicle)ElectricCar befindet sich zwei Ebenen unterhalb von Vehicle. Jeder Aufruf von super().__init__ steigt eine Ebene in der Kette auf, sodass alle drei __init__-Methoden ausgeführt werden.
Mehrfachvererbung
Python ermöglicht es einer Klasse, von mehr als einer Elternklasse zu erben. Listen Sie diese in den Klammern durch Kommas getrennt auf:
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return f"{self.name} makes a sound."
class Swimmer:
def swim(self):
return f"{self.name} swims."
class Flyer:
def fly(self):
return f"{self.name} flies."
class Duck(Animal, Swimmer, Flyer):
def speak(self):
return f"{self.name} says quack!"
donald = Duck("Donald")
print(donald.speak()) # Donald says quack!
print(donald.swim()) # Donald swims.
print(donald.fly()) # Donald flies.Mehrfachvererbung ist mächtig, aber komplex. Verwenden Sie Mixins — kleine, zweckgebundene Klassen, die eine einzige Fähigkeit hinzufügen —, um die Übersichtlichkeit zu wahren. Swimmer und Flyer oben sind genau dieses Muster.
Die Method Resolution Order (MRO)
Wenn Python eine Methode oder ein Attribut sucht, folgt es einer deterministischen Suchreihenfolge, der sogenannten Method Resolution Order (MRO). Bei einfacher Vererbung ist die Reihenfolge offensichtlich (Kind → Elternteil → Großelternteil → object). Bei Mehrfachvererbung verwendet Python den C3-Linearisierungsalgorithmus, um eine eindeutige Reihenfolge zu erzeugen.
Die MRO einer beliebigen Klasse kann mit .__mro__ oder help() überprüft werden:
print(Duck.__mro__)
# (<class 'Duck'>, <class 'Animal'>, <class 'Swimmer'>, <class 'Flyer'>, <class 'object'>)Die MRO ist besonders wichtig, wenn mehrere Elternklassen dieselbe Methode definieren. Python wählt die erste Klasse in der MRO, die sie definiert. Deshalb ist super() in Mehrfachvererbungshierarchien so wichtig: Jede Klasse in der MRO ruft super() auf, sodass jede Klasse in der Kette die Möglichkeit bekommt, ausgeführt zu werden.
class Base:
def greet(self):
print("Hello from Base")
class Left(Base):
def greet(self):
print("Hello from Left")
super().greet()
class Right(Base):
def greet(self):
print("Hello from Right")
super().greet()
class Child(Left, Right):
def greet(self):
print("Hello from Child")
super().greet()
Child().greet()
# Hello from Child
# Hello from Left
# Hello from Right
# Hello from BaseJeder Aufruf von super() folgt der MRO (Child → Left → Right → Base), sodass jedes greet() genau einmal ausgeführt wird, obwohl Base sowohl Elternteil von Left als auch von Right ist. Dies ist das Diamantproblem — Pythons MRO löst es sauber.
Vererbung zur Laufzeit prüfen
isinstance(obj, cls)
Gibt True zurück, wenn obj eine Instanz von cls oder einer ihrer Unterklassen ist:
tesla = ElectricCar("Tesla", "Model 3", 2024, 580)
print(isinstance(tesla, ElectricCar)) # True
print(isinstance(tesla, Car)) # True — Car is a parent
print(isinstance(tesla, Vehicle)) # True — Vehicle is a grandparent
print(isinstance(tesla, str)) # FalseDies ist zuverlässiger als der Vergleich type(obj) == Car, der bei Unterklassen False zurückgibt.
issubclass(sub, cls)
Gibt True zurück, wenn sub eine Unterklasse von cls ist (einschließlich sich selbst):
print(issubclass(ElectricCar, Vehicle)) # True
print(issubclass(Car, ElectricCar)) # False
print(issubclass(Car, Car)) # True — a class is a subclass of itselfHäufige Fallstricke
Vergessen, super().__init__() aufzurufen
Wenn das __init__ der Kindklasse super().__init__() nicht aufruft, werden die Attribute der Elternklasse nie gesetzt. Jede Methode, die diese Attribute erwartet, wirft einen AttributeError:
class Car(Vehicle):
def __init__(self, make, model, year, num_doors=4):
# forgot super().__init__(...)
self.num_doors = num_doors
c = Car("Toyota", "Camry", 2023)
c.start() # AttributeError: 'Car' object has no attribute 'make'Überschreiben ohne super(), obwohl Erweiterung beabsichtigt war
Wenn Sie eine Methode überschreiben und super() vergessen, wird die Version der Elternklasse nie ausgeführt. Dadurch geht Verhalten still verloren, von dem anderer Code möglicherweise abhängt.
Tiefe Mehrfachvererbungshierarchien
Mehr als zwei Ebenen der Mehrfachvererbung sind sehr schwer nachzuvollziehen. Wenn Sie sich beim Schreiben von class Foo(A, B, C, D) wiederfinden, erwägen Sie stattdessen Komposition — das Speichern von Instanzen als Attribute.
Vorteile der Vererbung
- Wiederverwendung von Code — gemeinsame Logik lebt an einem Ort; Unterklassen erben sie kostenlos.
- Erweiterbarkeit — Sie können Verhalten in einer Unterklasse hinzufügen oder ändern, ohne die Elternklasse zu berühren, sodass bestehende Aufrufer unverändert bleiben.
- Polymorphismus — Funktionen, die ein
Vehicleakzeptieren, funktionieren gleichermaßen mit jedemCar,MotorcycleoderElectricCar. Siehe Python Polymorphism für das vollständige Bild.