Vererbung

Einführung und Definitionen

Inheritance as DNA

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.



Classification of v
ehicles

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()
<__main__.Robot object at 0x000001BF27092408> <class '__main__.Robot'>
<__main__.PhysicianRobot object at 0x000001BF270923C8> <class '__main__.PhysicianRobot'>
Hi, Ich bin James


Marvin and James

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)
True True
False
True
False True

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))
True

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. "

Überschreiben

Kehren wir zu unserer neuen "PhysicianRobot" -Klasse zurück. Stellen Sie sich jetzt vor, eine Instanz eines PhysicianRobot sollte auf andere Weise Hallo sagen. In diesem Fall müssen wir die Methode say_hi innerhalb der Unterklasse PhysicianRobot neu definieren:

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()
Alles wird gut! 
James wird sich um dich kümmern!

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)
Alles wird gut! 
Dr James wird sich um dich kümmern!
... und jetzt die 'traditionelle' Roboter-Art, Hallo zu sagen :-)
Hi, ich bin Dr James

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)
health_level of Marvin0 before healing:  0.5562005305000016
Marvin0 has been healed by Dr. Frankenstein!
health_level of Marvin0 after healing:  0.7807651150204282
health_level of Marvin1 before healing:  0.40571527448692757
Marvin1 has been healed by Dr. Frankenstein!
health_level of Marvin1 after healing:  0.4160992532325318
health_level of Marvin2 before healing:  0.3786957462635925
Marvin2 has been healed by Dr. Frankenstein!
health_level of Marvin2 after healing:  0.5474124864506639
health_level of Marvin3 before healing:  0.6384666796845331
Marvin3 has been healed by Dr. Frankenstein!
health_level of Marvin3 after healing:  0.6986491928780778
health_level of Marvin4 before healing:  0.5983126049766974
Marvin4 has been healed by Dr. Frankenstein!
health_level of Marvin4 after healing:  0.6988801787833587
[('Marvin0', 0.7807651150204282), ('Marvin1', 0.4160992532325318), ('Marvin2', 0.5474124864506639), ('Marvin3', 0.6986491928780778), ('Marvin4', 0.6988801787833587)]

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()
Hi, ich bin Dr. Frankenstein
und ich bin ein Arzt

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()
Hi, ich bin Dr. Frankenstein
und ich bin ein Arzt!

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.

Unterscheidung zwischen Überschreiben, Überladen und Überschreiben

Überschreiben

Wenn wir eine Funktion überschreiben, ist die ursprüngliche Funktion nicht mehr vorhanden. Die Funktion wird neu definiert. Dieser Prozess hat nichts mit Objektorientierung oder Vererbung zu tun.

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))
45
46

Ü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:

In [ ]:
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:

 #include 
using 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))
49

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))
45 46

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))
45 4 50

Überschreiben

Das Überschreiben wurde bereits oben erklärt!