Verstehen der Klassenvererbung in Python 3
Einführung
Objektorientierte Programmierung schafft wiederverwendbare Code-Muster, um Redundanzen in Entwicklungsprojekten zu verringern. Eine Möglichkeit, wie objektorientierte Programmierung wiederverwendbaren Code erreicht, ist durch Vererbung, wenn eine Unterklasse Code von einer anderen Basisklasse nutzen kann.
Dieses Tutorial wird einige der wichtigsten Aspekte der Vererbung in Python durchgehen, einschließlich wie Eltern- und Kindklassen funktionieren, wie Methoden und Attribute überschrieben werden, wie die super()-Funktion verwendet wird und wie man Mehrfachvererbung nutzt.
Voraussetzungen
Sie sollten Python 3 installiert und eine Programmierumgebung auf Ihrem Computer oder Server eingerichtet haben. Wenn Sie keine Programmierumgebung eingerichtet haben, können Sie auf die Installations- und Einrichtungsanleitungen für eine lokale Programmierumgebung oder für eine Programmierumgebung auf Ihrem Server verweisen, die für Ihr Betriebssystem (Ubuntu, CentOS, Debian usw.) geeignet ist.
Was ist Erbschaft?
Vererbung ist, wenn eine Klasse Code verwendet, der in einer anderen Klasse konstruiert wurde. Wenn wir an Vererbung im biologischen Sinne denken, können wir uns ein Kind vorstellen, das bestimmte Eigenschaften von seinen Eltern erbt. Das heißt, ein Kind kann die Körpergröße oder die Augenfarbe eines Elternteils erben. Kinder können auch den gleichen Nachnamen wie ihre Eltern teilen.
Klassen, die Unterklassen oder Kindklassen genannt werden, erben Methoden und Variablen von Elternklassen oder Basis-Klassen.
Wir können an eine Elternklasse namens Parent denken, die Klassenvariablen für last_name, height und eye_color hat, die die Kindklasse Child von der Parent erben wird.
Da die Child-Unterklasse von der Parent-Basisklasse erbt, kann die Child-Klasse den Code von Parent wiederverwenden, was es dem Programmierer ermöglicht, weniger Codezeilen zu verwenden und die Redundanz zu verringern.
Elternklassen
Eltern- oder Basisklassen schaffen ein Muster, auf dem Kind- oder Unterklassen basieren können. Elternklassen ermöglichen es uns, Kindklassen durch Vererbung zu erstellen, ohne den gleichen Code jedes Mal erneut schreiben zu müssen. Jede Klasse kann in eine Elternklasse umgewandelt werden, sodass sie jeweils vollständig funktionsfähige Klassen für sich sind, anstatt nur Vorlagen.
Angenommen, wir haben eine allgemeine Bank_account Elternklasse, die die Kindklassen Personal_account und Business_account hat. Viele der Methoden zwischen Privat- und Geschäftskonten werden ähnlich sein, wie beispielsweise Methoden zum Abheben und Einzahlen von Geld, sodass diese zur Elternklasse Bank_account gehören können. Die Business_account Unterklasse hätte spezifische Methoden, einschließlich möglicherweise einer Möglichkeit, Geschäftsdokumente und -formulare zu sammeln, sowie einer employee_identification_number Variable.
In ähnlicher Weise kann eine Animal-Klasse die Methoden eating() und sleeping() haben, und eine Snake-Unterklasse kann ihre eigenen spezifischen Methoden hissing() und slithering() enthalten.
Lass uns eine Fish-Elternklasse erstellen, die wir später verwenden werden, um Fische als ihre Unterklassen zu konstruieren. Jeder dieser Fische wird neben Eigenschaften auch Vornamen und Nachnamen haben.
Info: Um dem Beispielcode in diesem Tutorial zu folgen, öffnen Sie eine Python-interaktive Shell auf Ihrem lokalen System, indem Sie den Befehl python3 ausführen. Dann können Sie die Beispiele kopieren, einfügen oder bearbeiten, indem Sie sie nach der >>> Eingabeaufforderung hinzufügen.
Wir werden eine neue Datei namens fish.py erstellen und mit der __init__() Konstruktormethode beginnen, die wir mit den Klassenvariablen first_name und last_name für jedes Fish Objekt oder jede Unterklasse füllen werden.
class Fish:
def __init__(self, first_name, last_name="Fish"):
self.first_name = first_name
self.last_name = last_name
Wir haben unsere last_name Variable mit dem String "Fish" initialisiert, weil wir wissen, dass die meisten Fische dies als ihren Nachnamen haben werden.
Lass uns auch einige andere Methoden hinzufügen:
class Fish:
def __init__(self, first_name, last_name="Fish"):
self.first_name = first_name
self.last_name = last_name
def swim(self):
print("The fish is swimming.")
def swim_backwards(self):
print("The fish can swim backwards.")
Wir haben die Methoden swim() und swim_backwards() zur Klasse Fish hinzugefügt, damit jede Unterklasse auch diese Methoden nutzen kann.
Da die meisten Fische, die wir erstellen werden, als knöcherne Fische betrachtet werden (da sie ein aus Knochen bestehendes Skelett haben) und nicht als knorpelige Fische (da sie ein aus Knorpel bestehendes Skelett haben), können wir dem __init__() Methoden ein paar weitere Attribute hinzufügen:
class Fish:
def __init__(self, first_name, last_name="Fish",
skeleton="bone", eyelids=False):
self.first_name = first_name
self.last_name = last_name
self.skeleton = skeleton
self.eyelids = eyelids
def swim(self):
print("The fish is swimming.")
def swim_backwards(self):
print("The fish can swim backwards.")
Den Bau einer Elternklasse folgt der gleichen Methodik wie der Bau jeder anderen Klasse, mit dem Unterschied, dass wir darüber nachdenken, welche Methoden die Kindklassen nutzen können, sobald wir diese erstellt haben.
Kind Klassen
Kind- oder Unterklassen sind Klassen, die von der Elternklasse erben. Das bedeutet, dass jede Kindklasse die Methoden und Variablen der Elternklasse nutzen kann.
Zum Beispiel kann eine Goldfisch Kindklasse, die von der Fisch Klasse erbt, die schwimmen() Methode, die in Fisch deklariert ist, nutzen, ohne sie selbst deklarieren zu müssen.
Wir können jede Kindklasse als eine Klasse der Elternklasse betrachten. Das heißt, wenn wir eine Kindklasse namens Rhombus und eine Elternklasse namens Parallelogram haben, können wir sagen, dass ein Rhombus ein Parallelogram ist, genau wie ein Goldfisch ein Fisch ist.
Die erste Zeile einer Kindklasse sieht ein wenig anders aus als die von Nicht-Kindklassen, da du die Elternklasse als Parameter in die Kindklasse übergeben musst:
class Trout(Fish):
Die Trout-Klasse ist ein Kind der Fish-Klasse. Wir wissen dies aufgrund des Auftretens des Wortes Fish in Klammern.
Mit Kindklassen können wir wählen, ob wir weitere Methoden hinzufügen, vorhandene Elternmethoden überschreiben oder die Standard-Elternmethoden mit dem pass-Stichwort akzeptieren, was wir in diesem Fall tun werden:
...
class Trout(Fish):
pass
Wir können jetzt ein Trout-Objekt erstellen, ohne zusätzliche Methoden definieren zu müssen.
...
class Trout(Fish):
pass
terry = Trout("Terry")
print(terry.first_name + " " + terry.last_name)
print(terry.skeleton)
print(terry.eyelids)
terry.swim()
terry.swim_backwards()
Wir haben ein Trout-Objekt terry erstellt, das jede der Methoden der Fish-Klasse nutzt, obwohl wir diese Methoden nicht in der Trout-Unterklasse definiert haben. Wir mussten nur den Wert von "Terry" an die Variable first_name übergeben, da alle anderen Variablen initialisiert waren.
Wenn wir das Programm ausführen, erhalten wir die folgende Ausgabe:
Terry Fish
bone
False
The fish is swimming.
The fish can swim backwards.
Als Nächstes erstellen wir eine weitere Kindklasse, die ihre eigene Methode enthält. Wir nennen diese Klasse Clownfisch, und ihre spezielle Methode ermöglicht es ihr, mit Seeanemonen zu leben:
...
class Clownfish(Fish):
def live_with_anemone(self):
print("The clownfish is coexisting with sea anemone.")
Als nächstes erstellen wir ein Clownfisch-Objekt, um zu sehen, wie das funktioniert:
...
casey = Clownfish("Casey")
print(casey.first_name + " " + casey.last_name)
casey.swim()
casey.live_with_anemone()
Wenn wir das Programm ausführen, erhalten wir die folgende Ausgabe:
Casey Fish
The fish is swimming.
The clownfish is coexisting with sea anemone.
Die Ausgabe zeigt, dass das Clownfish-Objekt casey in der Lage ist, die Fish-Methoden __init__() und swim() sowie die Kindklassenmethode live_with_anemone() zu verwenden.
Wenn wir versuchen, die Methode live_with_anemone() in einem Trout-Objekt zu verwenden, erhalten wir einen Fehler:
terry.live_with_anemone()
AttributeError: 'Trout' object has no attribute 'live_with_anemone'
Dies liegt daran, dass die Methode live_with_anemone() nur zur Kindklasse Clownfish gehört und nicht zur Elternklasse Fish.
Kindklassen erben die Methoden der zugehörigen Elternklasse, sodass jede Kindklasse diese Methoden in Programmen nutzen kann.
Überschreiben von Methoden der Elternklasse
Bisher haben wir die Kindklasse Trout betrachtet, die das Schlüsselwort pass verwendet hat, um alle Verhaltensweisen der Elternklasse Fish zu erben, sowie eine andere Kindklasse Clownfish, die alle Verhaltensweisen der Elternklasse geerbt hat und außerdem eine eigene einzigartige Methode geschaffen hat, die spezifisch für die Kindklasse ist. Manchmal möchten wir jedoch einige der Verhaltensweisen der Elternklasse nutzen, aber nicht alle. Wenn wir Methoden der Elternklasse ändern, überschreiben wir sie.
Beim Erstellen von Eltern- und Kindklassen ist es wichtig, das Programmdesign im Auge zu behalten, damit das Überschreiben keinen unnötigen oder redundanten Code erzeugt.
Wir werden eine Shark-Unterklasse der Fish-Oberklasse erstellen. Da wir die Fish-Klasse mit der Idee erstellt haben, hauptsächlich knöcherne Fische zu erstellen, müssen wir Anpassungen für die Shark-Klasse vornehmen, die stattdessen ein knorpeliger Fisch ist. In Bezug auf das Programmdesign, wenn wir mehr als einen nicht-knöchernen Fisch hätten, würden wir höchstwahrscheinlich separate Klassen für jede dieser beiden Fischarten erstellen wollen.
Haie haben, im Gegensatz zu knöchernen Fischen, ein Skelett aus Knorpel anstelle von Knochen. Sie haben auch Augenlider und können nicht rückwärts schwimmen. Haie können sich jedoch rückwärts bewegen, indem sie sinken.
In Anbetracht dessen werden wir die __init__()-Konstruktor-Methode und die swim_backwards()-Methode überschreiben. Wir müssen die swim()-Methode nicht ändern, da Haie Fische sind, die schwimmen können. Lassen Sie uns diese Kindklasse überprüfen:
...
class Shark(Fish):
def __init__(self, first_name, last_name="Shark",
skeleton="cartilage", eyelids=True):
self.first_name = first_name
self.last_name = last_name
self.skeleton = skeleton
self.eyelids = eyelids
def swim_backwards(self):
print("The shark cannot swim backwards, but can sink backwards.")
Wir haben die initialisierten Parameter in der __init__()-Methode überschrieben, sodass die Variable last_name nun auf den String "Shark" gesetzt ist, die Variable skeleton nun auf den String "cartilage" gesetzt ist und die Variable eyelids jetzt auf den booleschen Wert True gesetzt ist. Jede Instanz der Klasse kann auch diese Parameter überschreiben.
Die Methode swim_backwards() gibt jetzt einen anderen String aus als der in der Fish Elternklasse, da Haie nicht auf die gleiche Weise rückwärts schwimmen können wie knorpelfische.
Wir können jetzt eine Instanz der Shark Kindklasse erstellen, die weiterhin die swim() Methode der Fish Elternklasse nutzen wird:
...
sammy = Shark("Sammy")
print(sammy.first_name + " " + sammy.last_name)
sammy.swim()
sammy.swim_backwards()
print(sammy.eyelids)
print(sammy.skeleton)
Wenn wir diesen Code ausführen, erhalten wir die folgende Ausgabe:
Sammy Shark
The fish is swimming.
The shark cannot swim backwards, but can sink backwards.
True
cartilage
Die Shark-Kindklasse hat erfolgreich die __init__()- und swim_backwards()-Methoden der Fish-Elternklasse überschrieben und gleichzeitig die swim()-Methode der Elternklasse geerbt.
Wenn es eine begrenzte Anzahl von Kinderklassen gibt, die einzigartiger sind als andere, kann das Überschreiben von Methoden der Elternklasse nützlich sein.
Die super()-Funktion
Mit der super()-Funktion können Sie auf geerbte Methoden zugreifen, die in einem Klassenobjekt überschrieben wurden.
Wenn wir die super()-Funktion verwenden, rufen wir eine Methoden des Elternteils in einer Kindmethode auf, um sie zu nutzen. Zum Beispiel möchten wir möglicherweise einen Aspekt der Elternmethode mit einer bestimmten Funktionalität überschreiben, aber dann den Rest der ursprünglichen Elternmethode aufrufen, um die Methode zu beenden.
In einem Programm, das Schüler bewertet, möchten wir möglicherweise eine Kindklasse für Weighted_grade haben, die von der Elternklasse Grade erbt. In der Kindklasse Weighted_grade möchten wir möglicherweise eine Methode calculate_grade() der Elternklasse überschreiben, um die Funktionalität zur Berechnung einer gewichteten Note einzufügen, während wir dennoch den Rest der Funktionalität der ursprünglichen Klasse beibehalten. Durch den Aufruf der Funktion super() wären wir in der Lage, dies zu erreichen.
Die super() Funktion wird am häufigsten innerhalb der __init__() Methode verwendet, da dies der Ort ist, an dem Sie höchstwahrscheinlich etwas Einzigartigkeit zur Kindklasse hinzufügen und dann die Initialisierung vom Elternteil abschließen müssen.
Um zu sehen, wie das funktioniert, lassen Sie uns unsere Trout-Unterklasse ändern. Da Forellen typischerweise Süßwasserfische sind, fügen wir der __init__()-Methode eine water-Variable hinzu und setzen sie auf den String "Süßwasser", aber behalten Sie dann die restlichen Variablen und Parameter der Elternklasse:
...
class Trout(Fish):
def __init__(self, water = "freshwater"):
self.water = water
super().__init__(self)
...
Wir haben die __init__()-Methode in der Trout-Unterklasse überschrieben und eine andere Implementierung der __init__()-Methode bereitgestellt, die bereits von der übergeordneten Klasse Fish definiert ist. Innerhalb der __init__()-Methode unserer Trout-Klasse haben wir ausdrücklich die __init__()-Methode der Fish-Klasse aufgerufen.
Da wir die Methode überschrieben haben, müssen wir first_name nicht mehr als Parameter an Trout übergeben, und wenn wir einen Parameter übergeben würden, würden wir stattdessen freshwater zurücksetzen. Wir werden daher first_name initialisieren, indem wir die Variable in unserer Objektinstanz aufrufen.
Jetzt können wir die initialisierten Variablen der Elternklasse aufrufen und auch die einzigartige Kindvariable nutzen. Lassen Sie uns dies in einer Instanz von Forelle verwenden:
...
terry = Trout()
Initialize first name
terry.first_name = "Terry"
Use parent __init__() through super()
print(terry.first_name + " " + terry.last_name)
print(terry.eyelids)
Use child __init__() override
print(terry.water)
Use parent swim() method
terry.swim()
Terry Fish
False
freshwater
The fish is swimming.
Die Ausgabe zeigt, dass das Objekt terry der Kindklasse Trout sowohl die kind-spezifische __init__() Variable wasser nutzen kann, während es auch in der Lage ist, die __init__() Variablen der Elternklasse Fish wie first_name, last_name und eyelids aufzurufen.
Die eingebaute Python-Funktion super() ermöglicht es uns, Methoden der Elternklasse zu nutzen, selbst wenn wir bestimmte Aspekte dieser Methoden in unseren Kindklassen überschreiben.
Mehrfachvererbung
Mehrfachvererbung ist, wenn eine Klasse Attribute und Methoden von mehr als einer Elternklasse erben kann. Dies kann es Programmen ermöglichen, Redundanz zu reduzieren, kann jedoch auch eine gewisse Komplexität sowie Mehrdeutigkeit einführen, weshalb dies mit Bedacht auf das gesamte Programmdesign geschehen sollte.
Um zu zeigen, wie Mehrfachvererbung funktioniert, erstellen wir eine Coral_reef Kindklasse, die von einer Coral Klasse und einer Sea_anemone Klasse erbt. Wir können in jeder eine Methode erstellen und dann das pass Schlüsselwort in der Coral_reef Kindklasse verwenden:
class Coral:
def community(self):
print("Coral lives in a community.")
class Anemone:
def protect_clownfish(self):
print("The anemone is protecting the clownfish.")
class CoralReef(Coral, Anemone):
pass
Die Coral-Klasse hat eine Methode namens community(), die eine Zeile druckt, und die Anemone-Klasse hat eine Methode namens protect_clownfish(), die eine andere Zeile druckt. Dann rufen wir beide Klassen in das Vererbungs-Tupel auf. Das bedeutet, dass CoralReef von zwei Elternklassen erbt.
Lass uns jetzt ein CoralReef-Objekt instanziieren:
...
great_barrier = CoralReef()
great_barrier.community()
great_barrier.protect_clownfish()
Das Objekt great_barrier wird als ein CoralReef-Objekt festgelegt und kann die Methoden beider Elternklassen verwenden. Wenn wir das Programm ausführen, sehen wir die folgende Ausgabe:
Coral lives in a community.
The anemone is protecting the clownfish.
Die Ausgabe zeigt, dass Methoden aus beiden Elternklassen effektiv in der Kindklasse verwendet wurden.
Multiple Vererbung ermöglicht es uns, den Code von mehr als einer Elternklasse in einer Kindklasse zu verwenden. Wenn dieselbe Methode in mehreren Elternmethoden definiert ist, verwendet die Kindklasse die Methode der ersten Elternklasse, die in ihrer Tuple-Liste deklariert ist.
Obwohl Mehrfachvererbung effektiv genutzt werden kann, sollte sie mit Vorsicht erfolgen, damit unsere Programme nicht mehrdeutig und schwer verständlich für andere Programmierer werden.
Schlussfolgerung
Dieses Tutorial behandelte den Aufbau von Elternklassen und Kindklassen, das Überschreiben von Methoden und Attributen der Elternklasse innerhalb der Kindklasse, die Verwendung der super()-Funktion und die Möglichkeit für Kindklassen, von mehreren Elternklassen zu erben.
Die Vererbung in der objektorientierten Programmierung kann die Einhaltung des DRY-Prinzips (Don’t Repeat Yourself) in der Softwareentwicklung ermöglichen, wodurch mit weniger Code und Wiederholungen mehr erreicht werden kann. Vererbung zwingt Programmierer außerdem dazu, darüber nachzudenken, wie sie die Programme, die sie erstellen, gestalten, um sicherzustellen, dass der Code effektiv und klar ist.