Vererbung
Einführung und Definitionen
Keine objektorientierte Programmiersprache wäre es wert, betrachtet oder verwendet zu werden, wenn sie die Vererbung nicht unterstützt. Die Vererbung wurde 1969 für Simula erfunden. Python unterstützt nicht nur die Vererbung, sondern auch die Mehrfachvererbung. Im Allgemeinen ist Vererbung der Mechanismus, um neue Klassen von bestehenden abzuleiten. Auf diese Weise erhalten wir eine Hierarchie von Klassen. In den meisten klassenbasierten objektorientierten Sprachen erfasst ein durch Vererbung erstelltes Objekt (ein "untergeordnetes Objekt") - obwohl es in einigen Programmiersprachen Ausnahmen gibt - alle Eigenschaften und Verhaltensweisen des übergeordneten Objekts.
Durch Vererbung können Programmierer Klassen erstellen, die auf vorhandenen Klassen aufbauen. Auf diese Weise kann eine durch Vererbung erstellte Klasse die Attribute und Methoden der übergeordneten Klasse erben. Dies bedeutet, dass die Vererbung die Wiederverwendbarkeit von Code unterstützt. Die von einer Unterklasse geerbten Methoden oder allgemein die von einer Unterklasse geerbte Software gelten als in der Unterklasse wiederverwendet. Die Beziehungen von Objekten oder Klassen durch Vererbung führen zu einem gerichteten Graphen.
Die Klasse, von der eine Klasse erbt, wird als übergeordnete Klasse oder Oberklasse bezeichnet. Eine Klasse, die von einer Oberklasse erbt, wird als Unterklasse bezeichnet, auch als Erbenklasse oder Kinderklasse bezeichnet. Superklassen werden manchmal auch Vorfahren genannt. Es besteht eine hierarchische Beziehung zwischen Klassen. Es ähnelt Beziehungen oder Kategorisierungen, die wir aus dem wirklichen Leben kennen. Denken Sie zum Beispiel an Fahrzeuge. Fahrräder, Autos, Busse und Lastwagen sind Fahrzeuge. Pick-ups, Lieferwagen, Sportwagen, Cabrios und Kombis sind alles Autos und als Autos auch Fahrzeuge. Wir könnten eine Fahrzeugklasse in Python implementieren, die Methoden wie Beschleunigen und Bremsen haben könnte. Autos, Busse und Lastwagen und Fahrräder können als Unterklassen implementiert werden, die diese Methoden vom Fahrzeug erben.
Syntax der Vererbung in Python
Die Syntax für eine Unterklassendefinition sieht folgendermaßen aus:
class DerivedClassName(BaseClassName):
pass
Anstelle der Anweisung pass
gibt es Methoden und Attribute wie in allen anderen Klassen. Der Name BaseClassName muss in einem Bereich definiert werden, der die abgeleitete Klassendefinition enthält.
Jetzt sind wir bereit für ein einfaches Vererbungsbeispiel mit Python-Code.
Einfaches Vererbungsbeispiel
Wir werden uns an unsere geliebten Roboter oder besser an die "Roboter" -Klasse aus den vorherigen Kapiteln unseres Python-Tutorials halten, um zu zeigen, wie das Prinzip der Vererbung funktioniert. Wir werden eine Klasse PhysicianRobot
definieren, die von Robot
erbt.
class Robot:
def __init__(self, name):
self.name = name
def say_hi(self):
print("Hi, Ich bin " + self.name)
class PhysicianRobot(Robot):
pass
x = Robot("Marvin")
y = PhysicianRobot("James")
print(x, type(x))
print(y, type(y))
y.say_hi()
Wenn Sie sich den Code unserer PhysicianRobot
-Klasse ansehen, können Sie sehen, dass wir in dieser Klasse keine Attribute oder Methoden definiert haben. Da die Klasse PhysicianRobot
eine Unterklasse von Robot
ist, erbt sie in diesem Fall sowohl die Methode __init__
als auch say_hi
. Das Erben dieser Methoden bedeutet, dass wir sie so verwenden können, als ob sie in der Klasse "PhysicianRobot" definiert wären. Wenn wir eine Instanz von PhysicianRobot
erstellen, erstellt die Funktion __init__
auch ein Namensattribut. Wir können die say_hi
Methode auf das PhysisicianRobot
Objekt y
anwenden, wie wir in der Ausgabe des obigen Codes sehen können.
Unterschied zwischen type
und isinstance
Sie sollten auch die folgenden Fakten beachten, auf die wir auch in anderen Abschnitten unseres Python-Tutorials hingewiesen haben. Die Leute fragen häufig, wo der Unterschied zwischen der Überprüfung des Typs über die Funktion type
oder der Funktion isinstance
liegt.
Der Unterschied ist im folgenden Code zu sehen. Wir sehen, dass isinstance
True
zurückgibt, wenn wir ein Objekt entweder mit der Klasse vergleichen, zu der es gehört, oder mit der Oberklasse. Während der Gleichheitsoperator nur "True" zurückgibt, wenn wir ein Objekt mit seiner eigenen Klasse vergleichen.
x = Robot("Marvin")
y = PhysicianRobot("James")
print(isinstance(x, Robot), isinstance(y, Robot))
print(isinstance(x, PhysicianRobot))
print(isinstance(y, PhysicianRobot))
print(type(y) == Robot, type(y) == PhysicianRobot)
Dies gilt sogar für beliebige Vorfahren der Klasse in der Vererbungszeile:
class A:
pass
class B(A):
pass
class C(B):
pass
x = C()
print(isinstance(x, A))
Jetzt sollte klar sein, warum PEP 8, der offizielle Style Guide für Python-Code, sagt: "Objekttypvergleiche sollte immer isinstance () verwenden, anstatt Typen direkt zu vergleichen. "
class Robot:
def __init__(self, name):
self.name = name
def say_hi(self):
print("Hi, ich bin " + self.name)
class PhysicianRobot(Robot):
def say_hi(self):
print("Alles wird gut! ")
print(self.name + " wird sich um dich kümmern!")
y = PhysicianRobot("James")
y.say_hi()
Was wir im vorherigen Beispiel getan haben, wird als Überschreiben bezeichnet. Eine Methode einer übergeordneten Klasse wird überschrieben, indem einfach eine Methode mit demselben Namen in der untergeordneten Klasse definiert wird.
Wenn eine Methode in einer Klasse überschrieben wird, kann weiterhin auf die ursprüngliche Methode zugegriffen werden. Wir müssen dies jedoch tun, indem wir die Methode direkt mit dem Klassennamen aufrufen, d. H. Robot.say_hi (y)
. Wir demonstrieren dies im folgenden Code:
y = PhysicianRobot("Dr James")
y.say_hi()
print("... und jetzt die 'traditionelle' Roboter-Art, Hallo zu sagen :-)")
Robot.say_hi(y)
Wir haben gesehen, dass eine geerbte Klasse Methoden von der Oberklasse erben und überschreiben kann. Außerdem benötigt eine Unterklasse häufig zusätzliche Methoden mit zusätzlichen Funktionen, die in der Oberklasse nicht vorhanden sind. Eine Instanz der Klasse "PhysicianRobot" benötigt zum Beispiel die Methode "heilen", damit der Arzt einen ordnungsgemäßen Job machen kann.
Wir werden der Robot
-Klasse auch ein Attribut health_level
hinzufügen, das einen Wert zwischen 0
und 1
annehmen kann. Die Roboter werden mit einem zufälligen Wert zwischen "0" und "1" zum Leben erweckt. Wenn der Gesundheitszustand eines Roboters unter 0,8 liegt, benötigt er einen Arzt. Wir schreiben eine Methode need_a_doctor
, die True
zurückgibt, wenn der Wert unter 0.8
liegt und andernfalls False
. Die "Heilung" in der "Heilung" -Methode erfolgt durch Setzen des "Gesundheitsniveaus" auf einen zufälligen Wert zwischen dem alten "Gesundheitsniveau" und 1. Dieser Wert wird von der Uniform berechnet Funktion des Zufallsmoduls.
import random
class Robot:
def __init__(self, name):
self.name = name
self.health_level = random.random()
def say_hi(self):
print("Hi, I am " + self.name)
def needs_a_doctor(self):
if self.health_level < 0.8:
return True
else:
return False
class PhysicianRobot(Robot):
def say_hi(self):
print("Alles wird gut! ")
print(self.name + " kümmert sich um dich!")
def heal(self, robo):
robo.health_level = random.uniform(robo.health_level, 1)
print(robo.name + " wurde geheilt von " + self.name + "!")
doc = PhysicianRobot("Dr. Frankenstein")
rob_list = []
for i in range(5):
x = Robot("Marvin" + str(i))
if x.needs_a_doctor():
print("Gesundheitsniveau von " + x.name + " vor der Heilung: ", x.health_level)
doc.heal(x)
print("Gesundheitsniveau von " + x.name + " nach der Heilung: ", x.health_level)
rob_list.append((x.name, x.health_level))
print(rob_list)
Wenn wir eine Methode überschreiben, möchten wir manchmal die Methode der übergeordneten Klasse und einige neue Dinge wiederverwenden. Um dies zu demonstrieren, werden wir eine neue Version des PhysicianRobot schreiben. say_hi
sollte den Text aus der Robot-Klassenversion plus den Text" und ich bin ein Arzt! "zurückgeben.
class PhysicianRobot(Robot):
def say_hi(self):
Robot.say_hi(self)
print("und ich bin ein Arzt")
doc = PhysicianRobot("Dr. Frankenstein")
doc.say_hi()
Wir wollen keinen redundanten Code schreiben und haben deshalb Robot.say_hi (self)
aufgerufen. Wir könnten auch die super
Funktion verwenden:
class PhysicianRobot(Robot):
def say_hi(self):
super().say_hi()
print("und ich bin ein Arzt!")
doc = PhysicianRobot("Dr. Frankenstein")
doc.say_hi()
super
ist in diesem Fall nicht wirklich notwendig. Man könnte argumentieren, dass es den Code wartbarer macht, weil wir den Namen der übergeordneten Klasse ändern könnten, aber dies wird in vorhandenen Klassen sowieso selten gemacht. Der wahre Vorteil von super
zeigt sich, wenn wir es mit Mehrfachvererbung verwenden.
def f(x):
return x + 42
print(f(3))
# f wird im Folgenden überschrieben (oder neu definiert):
def f(x):
return x + 43
print(f(3))
Überladen
Dieses Unterkapitel ist nur für C ++ - und Java-Programmierer interessant, die wissen möchten, wie eine Überladung in Python erreicht werden kann. Wer nichts über Überladung weiß, wird es nicht verpassen!
Im Zusammenhang mit der objektorientierten Programmierung haben Sie möglicherweise auch von "Überladung" gehört. Auch wenn "Überladung" nicht direkt mit OOP verbunden ist. Überladen ist die Möglichkeit, eine Funktion mit demselben Namen mehrmals zu definieren. Die Definitionen unterscheiden sich hinsichtlich der Anzahl der Parameter und der Arten der Parameter. Es ist die Fähigkeit einer Funktion, je nach Anzahl der Parameter oder Art der Parameter unterschiedliche Aufgaben auszuführen. Wir können solche Funktionen in Python nicht überladen, aber es ist auch nicht notwendig.
In diesem Kurs geht es jedoch nicht um C ++, und wir haben bisher die Verwendung von C ++ - Code vermieden. Wir möchten jetzt eine Ausnahme machen, damit Sie sehen können, wie das Überladen in C ++ funktioniert.
#include#include using namespace std; int successor(int number) { return number + 1; } double successor(double number) { return number + 1; } int main() { cout << successor(10) << endl; cout << successor(10.3) << endl; return 0; }
Wir haben die Nachfolgerfunktion zweimal definiert: Einmal für int und einmal mit float als Parameter. In Python kann die Funktion wie folgt definiert werden, wie Sie sicher wissen:
def successor(x):
return x + 1
Da x nur eine Referenz auf ein Objekt ist, kann der Python-Funktionsnachfolger mit jedem Objekt aufgerufen werden, obwohl Ausnahmen mit vielen Typen erstellt werden. Aber es wird mit int und float Werten funktionieren!
Eine Funktion mit einer anderen Anzahl von Parametern ist eine weitere Möglichkeit der Funktionsüberladung. Das folgende C ++ - Programm zeigt ein solches Beispiel. Die Funktion f kann mit einem oder zwei ganzzahligen Argumenten aufgerufen werden:
#includeusing namespace std; int f(int n); int f(int n, int m); int main() { cout << "f(3): " << f(3) << endl; cout << "f(3, 4): " << f(3, 4) << endl; return 0; } int f(int n) { return n + 42; } int f(int n, int m) { return n + m + 42; }
Dies funktioniert in Python nicht, wie wir im folgenden Beispiel sehen können. Die zweite Definition von f mit zwei Parametern definiert die erste Definition mit einem Argument neu oder überschreibt sie. Überschreiben bedeutet, dass die erste Definition nicht mehr verfügbar ist.
def f(n):
return n + 42
def f(n,m):
return n + m + 42
print(f(3, 4))
Wenn Sie f mit nur einem Parameter aufrufen, wird eine Ausnahme ausgelöst:
f(3) --------------------------------------------------------------------------- TypeError Traceback (most recent call last)in ----> 1 f(3) TypeError: f() missing 1 required positional argument: 'm'
In diesem Fall ist es jedoch möglich, das Überladungsverhalten von C ++ in Python mit einem Standardparameter zu simulieren:
def f(n, m=None):
if m:
return n + m +42
else:
return n + 42
print(f(3), f(1, 3))
Der Operator * kann als allgemeinerer Ansatz für eine Funktionsfamilie mit 1, 2, 3 oder noch mehr Parametern verwendet werden:
def f(*x):
if len(x) == 1:
return x[0] + 42
elif len(x) == 2:
return x[0] - x[1] + 5
else:
return 2 * x[0] + x[1] + 42
print(f(3), f(1, 2), f(3, 2, 1))