Home | Lehre | Videos | Texte | Vorträge | Software | Person | Impressum, Datenschutzerklärung | ![]()
Stand: 2025-05-15
weitgehend formuliert von ChatGPT 4, verbessert von ChatGPT 4o,
redigiert/korrigiert von Jörn Loviscach
Vererbung ist ein Grundkonzept der objektorientierten Programmierung. Sie ermöglicht, eine neue Klasse (Unterklasse) zu erstellen, die die Eigenschaften und Methoden einer bestehenden Klasse (Oberklasse) übernimmt. Dies hilft, Code wiederzuverwenden und die Struktur von Programmen klarer und wartbarer zu gestalten.
Angenommen, wir möchten ein Bestellsystem für einen Online-Shop
entwickeln. Wir haben eine allgemeine Klasse Produkt, und
daraus abgeleitet spezifische Klassen für verschiedene Produkttypen wie
Buch und Elektronik.
Produktclass Produkt:
def __init__(self, name, preis):
self.name = name
self.preis = preis
def zeige_info(self):
print(f'Produkt: {self.name}, Preis: {self.preis:.2f} EUR')Produkt-Klasse hat eine Initialisierungsfunktion
(__init__), welche die Attribute name und
preis anlegt.zeige_info gibt die grundlegenden
Informationen des Produkts aus.zeige_info-Methode
schreiben. Siehe unten zu __str__. Aber eines nach dem
anderen!)
class Buch(Produkt):
def __init__(self, name, preis, autor):
super().__init__(name, preis)
self.autor = autor
class Elektronik(Produkt):
def __init__(self, name, preis, garantie):
super().__init__(name, preis)
self.garantie = garantieBuch-Klasse erbt von Produkt und fügt
ein neues Attribut autor hinzu.Elektronik-Klasse erbt ebenfalls von
Produkt und fügt ein neues Attribut garantie
hinzu.super() wird benutzt, um auf Methoden der Oberklasse =
superclass zuzugreifen, die in der Unterklasse überschrieben sind. So
sorgt super().__init__(name, preis) in den Unterklassen
dafür, dass der Initialisierer der Oberklasse aufgerufen wird und dann
die Attribute name und preis anlegt.
Vorsicht: self ist eine Variable (die man zur Verwirrung
auch anders nennen dürfte), aber super() ein
Funktionsaufruf, also mit Klammern dahinter.
Und nochmal Vorsicht: Das Folgende ist erlaubt, denn
super().__init__(...) ist ein ganz normaler
Funktionsaufruf. Dass man in der Unterklasse die Variablennamen
name und preis wie in der Oberklasse
verwendet, dient nur dem Verständnis.
def __init__(mein_Selbst, komischer_name, preis, garantie):
super().__init__(komischer_name, 2 * preis)
mein_Selbst.garantie = garantie# Erstellen von Produktinstanzen
produkt1 = Produkt('Allgemeines Produkt', 19.99)
buch1 = Buch('Der Python-Kurs', 29.99, 'John Doe')
elektronik1 = Elektronik('Smartphone', 399.99, 2)
# Informationen anzeigen
produkt1.zeige_info()
# Ausgabe:
# Produkt: Allgemeines Produkt, Preis: 19.99 EUR
buch1.zeige_info()
# Ausgabe:
# Produkt: Der Python-Kurs, Preis: 29.99 EUR
elektronik1.zeige_info()
# Ausgabe:
# Produkt: Smartphone, Preis: 399.99 EURDurch die Verwendung von Vererbung können wir die allgemeine
Funktionalität in der Produkt-Klasse definieren und dann
spezialisierte Klassen wie Buch und Elektronik
erstellen, die diese Funktionalität erweitern oder anpassen. Dies führt
zu einem klaren und übersichtlichen Code, der leicht zu erweitern und zu
warten ist.
Produkt ist die Oberklasse = Basisklasse = Elternklasse
der Klassen Buch und Elektronik. Diese
letzteren beiden Klassen sind Unterklassen = Ableitungen = Kindklassen
von Produkt.
Das Spiel lässt sich weitertreiben, indem etwa eine Klasse
Notebook von der Klasse Elektronik erbt.
Notebook wäre dann eine Unterklasse einer Unterklasse von
Produkt. Nein, man sagt nicht Enkelklasse
dazu.
;-)
Das Überschreiben (englisch overriding
) von Methoden bedeutet,
dass eine Methode in einer abgeleiteten Klasse eine Methode der
Basisklasse mit demselben Namen ersetzt. Dadurch kann das Verhalten der
Methode in der abgeleiteten Klasse spezifiziert oder angepasst
werden.
Das Überschreiben von Methoden ist aus mehreren Gründen nützlich:
Spezialisierung des Verhaltens: Es ermöglicht einer abgeleiteten Klasse, das Verhalten der geerbten Methoden zu spezifizieren und zu ändern.
Polymorphismus: Es unterstützt das Konzept des Polymorphismus, bei dem eine Methode unterschiedlich implementiert wird, basierend darauf, welches Objekt sie aufruft. (Unten mehr dazu.)
Wiederverwendbarkeit und Wartbarkeit: Durch das Überschreiben können wir Code wiederverwenden und dennoch flexible und erweiterbare Entwürfe erstellen, ohne die Basisklasse ändern zu müssen.
Um keine Verwirrung hervorzurufen, sollte die überschreibende Methode dieselben Typen an Parametern nehmen wie die Oberklasse. (Etwa in C++ und Java ist das eine Pflicht für das Überschreiben.)
Ein beliebter Fehler: Vertippt man sich in der Unterklasse beim Namen
der Methode, überschreibt man nicht die Methode der Oberklasse, sondern
legt in der Unterklasse eine ganz neue Methode an. Um diesen Fehler
automatisch entdecken zu lassen, kann man ab Python 3.12 in die Zeile
vor die Definition der überschreibenden Methode in der Unterklasse ein
@override schreiben. Dazu ist derzeit noch vorher ein
from typing import override nötig.
Das Beispiel von oben lässt sich fortsetzen, indem man die
zeige_info-Methode überschreibt Die
Produkt-Klasse definiert ja eine allgemeine Methode
zeige_info, die grundlegende Informationen über das Produkt
anzeigt:
class Produkt:
def __init__(self, name, preis):
self.name = name
self.preis = preis
def zeige_info(self):
print(f'Produkt: {self.name}, Preis: {self.preis:.2f} EUR')Die Buch-Klasse erbt von Produkt und fügt
das Attribut autor hinzu. Die Klasse Buch kann
nun die Methode zeige_info von Produkt
überschreiben, um die spezifischen Informationen des Buches anzuzeigen.
super().zeige_info() ruft die
zeige_info-Methode der Oberklasse auf, um zunächst die
allgemeinen Produktinformationen anzuzeigen:
class Buch(Produkt):
def __init__(self, name, preis, autor):
super().__init__(name, preis)
self.autor = autor
def zeige_info(self):
super().zeige_info()
print(f'Autor: {self.autor}')Entsprechendes kann in der Methode zeige_info der
Elektronik-Klasse passieren:
class Elektronik(Produkt):
def __init__(self, name, preis, garantie):
super().__init__(name, preis)
self.garantie = garantie
def zeige_info(self):
super().zeige_info()
print(f'Garantie: {self.garantie} Jahre')Die Ausgabe im Beispiel von oben ist damit nun:
buch1.zeige_info()
# Ausgabe:
# Produkt: Der Python-Kurs, Preis: 29.99 EUR
# Autor: John Doe
elektronik1.zeige_info()
# Ausgabe:
# Produkt: Smartphone, Preis: 399.99 EUR
# Garantie: 2 JahreDurch das Überschreiben der zeige_info-Methode in den
abgeleiteten Klassen können wir spezifische Informationen für
unterschiedliche Produkttypen anzeigen, ohne die allgemeine Logik der
Basisklasse Produkt zu verändern. Dies zeigt, wie Vererbung
und Überschreiben zusammenarbeiten, um flexiblen und erweiterbaren Code
zu erstellen.
Warum würde beim Überschreiben oben
self.zeige_info() statt super().zeige_info()
Unsinn sein?
__str__ und
__repr__Jede Klasse in Python erbt direkt oder über ihre Oberklasse die
Methoden __str__ und __repr__. Diese
bestimmen, wie eine Instanz der Klasse als Zeichenkette dargestellt
wird.
__str__ dient dazu, eine möglichst lesefreundliche
Zeichenkette (string) zu liefern. Es wird zum Beispiel
automatisch in print() verwendet. Das ist die
professionelle Lösung statt unserer Methode zeige_info von
oben.
__repr__ ist dafür gedacht, eine Zeichenkette mit
einer möglichst genauen Darstellung (representation) zu
liefern, zum Beispiel zur Fehlersuche.
Wenn man diese Methoden nicht überschreibt, zeigt Python
standardmäßig etwa bei print() nur etwas wie
<MeineKlasse object at 0x7f...> – das ist meist nicht
hilfreich. Deshalb überschreibt man diese beiden Methoden in eigenen
Klassen fast immer:
class Auto:
def __init__(self, marke, baujahr):
self.marke = marke
self.baujahr = baujahr
def __str__(self):
return f'{self.marke} aus {self.baujahr}'
def __repr__(self):
return f'Auto("{self.marke}", {self.baujahr})'a = Auto('Opel', 2015)
print(a) # Ausgabe: Opel aus 2015
a # Ausgabe: Auto("Opel", 2015)Die schon bekannte Initialisierungsmethode __ini__ ist
ebenfalls von dieser Art: Auch sie wird von der Oberklasse geerbt und
fast immer überschrieben. (Die Konstruktoren in den Sprachen C++, Java,
C# werden nicht vererbt und deshalb auch nicht
überschrieben.)
Polymorphie ist ein fundamentales Konzept in der objektorientierten Programmierung, das es Objekten verschiedener Klassen ermöglicht, auf die gleiche Art angesprochen zu werden. Eine der Hauptideen hinter Polymorphie ist, dass Objekte einer Unterklasse (und Unter-Unterklasse usw.) überall dort verwendet werden können, wo Objekte der Oberklasse erwartet werden. Das heißt, Unterklassen (Kindklassen) können ihre jeweiligen Oberklassen (Elternklassen) vertreten. Die Unterklassen können dabei aber zusätzliche Attribute und Methoden haben und sie können Methoden ihrer jeweiligen Oberklasse überschreiben und damit den Effekt dieser Methoden ändern.
Der Online-Shop aus dem bereits verwendeten Beispiel benötigt einen
Datenspeicher für den Gesamtbestand an Produkten. Hierbei verwenden wir
eine Liste, die Objekte verschiedener Produktklassen enthält, aber
dennoch als Liste von Produkt-Objekten betrachtet wird.
Dank Polymorphie können wir eine Liste von Produkten definieren, die
sowohl Produkt, Buch als auch
Elektronik-Objekte enthält, denn Buch und
Elektronik können ihre Oberklasse Produkt
vertreten. Die Methode zeige_info wird korrekt für jeden
Produkttyp aufgerufen.
# Liste von verschiedenen Produktobjekten
produkte: list[Produkt] = [
Produkt('Allgemeines Produkt', 19.99),
Buch('Der Python-Kurs', 29.99, 'John Doe'),
Elektronik('Smartphone', 399.99, 2)
]
# Informationen anzeigen für alle Produkte in der Liste
for produkt in produkte:
produkt.zeige_info()
print() # Leere Zeile zur besseren LesbarkeitFlexibilität: Die Methode
zeige_info kann für verschiedene Produkttypen verwendet
werden, ohne dass die spezifischen Typen bekannt sein müssen.
Erweiterbarkeit: Neue Produkttypen können leicht
hinzugefügt werden, indem einfach neue Klassen erstellt werden, die von
Produkt erben.
Klarheit und Wartbarkeit: Der Code ist klarer und modularer, da er allgemeine Operationen abstrahiert und spezialisierte Implementierungen verbirgt.
Durch Polymorphie können wir eine einheitliche Schnittstelle (Sammlung der Methoden) für verschiedene Produkttypen bereitstellen, was zu einem flexiblen, erweiterbaren und gut strukturierten Code führt.
Sprachen wie C++ und Java sind sehr strikt darin, festzulegen, welche Klassen auf welche Bestandteile welcher anderen Klassen zugreifen können. In Python ist das viel lockerer. Unterstriche vor den Namen von Attributen und Methoden spielen dabei die Hauptrolle.
_)Ein einzelner Unterstrich am Anfang eines Namens ist ein Hinweis,
dass das jeweilige Element als geschützt betrachtet werden soll,
sozusagen unter der Motorhaube liegend oder versehen mit der Aufschrift
Finger weg!
. Es soll nicht von außerhalb der Klasse direkt
angesprochen werden. Das ist wohlgemerkt nur ein soll
,
denn Python verbietet diesen Zugriff nicht.
Der Gedanke hinter diesem (zugegebenermaßen schwachen) Schutz ist, dass man dank ihm unter der Motorhaube der Klasse Änderungen vornehmen kann, ohne auf andere Klassen Rücksicht nehmen zu müssen. Das gelingt um so besser, je mehr Attribute und Methoden man mit dem Unterstrich als geschützt markiert und je mehr sich andere Klassen an diesen Hinweis halten, also diese Attribute und Methoden nicht direkt ansprechen.
Ein Beispiel:
class Beispiel:
def __init__(self):
self._geschütztes_attribut = 42
def _geschützte_methode(self):
print('Diese Methode ist geschützt.')
obj = Beispiel()
print(obj._geschütztes_attribut) # 42, aber eigentlich sollte man das nicht machen
obj._geschützte_methode() # Geht, aber eigentlich sollte man das nicht machenIn diesem Beispiel werden _geschütztes_attribut und
_geschützte_methode mit einem einfachen Unterstrich
versehen, um anzuzeigen, dass sie nicht außerhalb der Klasse verwendet
werden sollten.
__)Doppelte Unterstriche vor einem Attribut oder einer Methode bieten
erstens einen (minimal) stärkeren Schutz und ersparen zweitens Stress
beim Ableiten von Klassen. Doppelte Unterstriche führen anders als der
einfache Unterstrich zu einer Aktion der Python-Laufzeitumgebung: Intern
wird dem jeweiligen Namen der Name der Klasse vorangestellt, mit einem
führenden Unterstrich. (Dieser Mechanismus gilt nicht für Namen wie
__str__, die obendrein zwei oder mehr Unterstriche am
Ende haben.)
Dies ist keine ernstzunehmende Schutzmaßnahme, um das Attribut oder die Methode zu verbergen, aber es erleichtert das Ableiten: Eine Unterklasse kann die gleichen Namen für eigene weitere Attribute und Methoden verwenden wie ihre Oberklasse; sie werden dann zusätzlich angelegt, weil sie ja intern anders heißen. Das vermeidet Konflikte.
Doppelte Unterstriche sind vor allem in tiefen Klassenhierarchien mit Kindeskindeskinderklassen usw. hilfreich, in denen man den Überblick darüber verliert, welche Attribute und Methoden die Oberklassen und Oberoberklassen usw. haben, oder in denen die Gefahr besteht, dass später in einer Oberklasse ein Attribut oder eine Methode ergänzt wird, dessen/deren Namen es schon in einer der Unterklassen gibt.
Ein Beispiel:
class Beispiel:
def __init__(self):
self.__privates_attribut = 42
def __private_methode(self):
print('Diese Methode ist privat.')
def zugriff_methode(self):
self.__private_methode()
obj = Beispiel()
# Direkter Zugriff schlägt fehl:
# print(obj.__privates_attribut) # Fehler
# obj.__private_methode() # Fehler
# Der Zugriff ist mit ein wenig mehr Mühe aber möglich:
print(obj._Beispiel__privates_attribut) # 42
obj._Beispiel__private_methode() # Diese Methode ist privat.Alle bisher erwähnten Attribute und Methoden bezogen sich jeweils auf
eine Instanz, also auf ein Objekt der Klasse: Jede Instanz der Klasse
besitzt eigene Werte für diese Attribute und hat in den Methoden die
Variable self, die auf die aktuelle Instanz verweist. In
der Objektorientierung unterscheidet man solche
Instanz-Attribute und -Methoden, die spezifisch für eine
Instanz (ein Objekt) einer Klasse sind, von Klassen-Attributen
und -Methoden, die für die gesamte Klasse und alle ihre Instanzen
gelten. Letztere heißen oft auch statisch.
In Python gibt es (anderes als in den meisten anderen Sprachen) eine Unterscheidung zwischen Klassenmethoden und statischen Methoden. Erstere können direkt auf Klassenattribute zugreifen, letztere nicht.
In Python wird ein Klassen-Attribut in der Klassendefinition außerhalb aller Methoden definiert:
class Fahrzeug:
# Klassen-Attribut
anzahl_fahrzeuge = 0
def __init__(self, marke, modell):
# Instanz-Attribute
self.marke = marke
self.modell = modell
# Erhöhen der Anzahl der Fahrzeuge bei jeder Instanziierung
Fahrzeug.anzahl_fahrzeuge += 1
# Instanziiere Fahrzeuge
auto1 = Fahrzeug('Toyota', 'Corolla')
auto2 = Fahrzeug('Honda', 'Civic')
print(f'Anzahl der Fahrzeuge: {Fahrzeug.anzahl_fahrzeuge}') # Ausgabe: 2Eine Klassen-Methode wird mit dem Decorator
@classmethod und einem speziellen ersten Parameter
cls (der auf die Klasse selbst verweist) definiert:
class Fahrzeug:
anzahl_fahrzeuge = 0
def __init__(self, marke, modell):
self.marke = marke
self.modell = modell
Fahrzeug.anzahl_fahrzeuge += 1
@classmethod
def get_anzahl_fahrzeuge(cls):
return cls.anzahl_fahrzeuge
# Instanziiere Fahrzeuge
auto1 = Fahrzeug('Toyota', 'Corolla')
auto2 = Fahrzeug('Honda', 'Civic')
print(f'Anzahl der Fahrzeuge: {Fahrzeug.get_anzahl_fahrzeuge()}') # Ausgabe: 2In einer Klassenmethode hat also cls die Rolle des
self in einer Instanzmethode. Eine Instanzmethode wird
mittels einer konkreten Instanz aufgerufen wird, nach dem Muster
auto1.tu_etwas(). Eine Klassenmethode wird dagegen über die
Klasse selbst aufgerufen: Fahrzeug.tu_etwas().
Anders als eine Klassenmethode arbeitet eine statische Methode
unabhängig von der Klasse und ihren Instanzen, hat also keinen Zugriff
auf cls oder self. Sie wird mit dem Dekorator
@staticmethod definiert und ebenfalls über die Klasse
selbst aufgerufen, nicht über eine Instanz:
class Fahrzeug:
anzahl_fahrzeuge = 0
def __init__(self, marke, modell):
self.marke = marke
self.modell = modell
Fahrzeug.anzahl_fahrzeuge += 1
@classmethod
def get_anzahl_fahrzeuge(cls):
return cls.anzahl_fahrzeuge
@staticmethod
def beispiel_static_method():
print('Dies ist eine statische Methode.')
# Aufruf der statischen Methode
Fahrzeug.beispiel_static_method() # Ausgabe: Dies ist eine statische Methode.In Python enthalten alle Variablen Referenzen und nicht direkt Werte: Jede Variable zeigt auf ein Objekt und enthält nie den Wert selbst. Das ist anders als in C, C++, Java und vielen anderen Sprachen, in denen insbesondere Zahlenvariablen direkt Werte speichern, statt (etwa als Zeiger) auf Werte zu verweisen.
Python arbeitet also so, als ob man in C ausschließlich Zeiger (Pointer) nutzen würde. Das kann für Überraschungen sorgen, weil man dasselbe Objekt über verschiedene Referenzen ansprechen und ändern kann.
Mehrere Variablen können auf dasselbe Objekte verweisen. Dies bedeutet, dass, wenn man ein Objekt über einer dieser Variable ändert, diese Änderung ebenfalls über die anderen Variablen sichtbar ist:
liste1 = [1, 2, 3]
liste2 = liste1 # Es gibt weiterhin nur eine Liste, aber nun zwei Verweise darauf!
liste2.append(4)
print(liste1) # Ausgabe: [1, 2, 3, 4]
print(liste2) # Ausgabe: [1, 2, 3, 4]In diesem Beispiel verweisen sowohl liste1 als auch
liste2 auf dasselbe Listenobjekt. Wenn man
liste2 ändert, wird liste1 ebenfalls geändert,
da beide Variablen auf dasselbe Objekt verweisen. Korrekterweise dürfte
man sowieso nicht sagen ich ändere
, sondern
man müsste sagen liste2ich ändere das Objekt, auf das
.liste2
verweist
Wenn zwei Referenzen auf dieselbe Instanz einer Klasse zeigen, können Änderungen über eine Referenz unerwartete Auswirkungen darauf haben, was man unter der anderen Referenz sieht. Dies kann besonders verwirrend sein, wenn man nicht bewusst ist, dass beide Referenzen tatsächlich auf dasselbe Objekt im Speicher zeigen.
Stellen wir uns ein Szenario vor, bei dem wir eine Klasse
Konto haben, die ein Bankkonto darstellt. Wir erstellen ein
Kontoobjekt und verwenden zwei verschiedene Referenzen darauf.
class Konto:
def __init__(self, inhaber: str, kontostand: float):
self.inhaber = inhaber
self.kontostand = kontostand
def einzahlen(self, betrag: float):
self.kontostand += betrag
def abheben(self, betrag: float):
if betrag <= self.kontostand:
self.kontostand -= betrag
else:
print('Nicht genügend Guthaben.')
def zeige_info(self):
print(f'Inhaber: {self.inhaber}, Kontostand: {self.kontostand:.2f} EUR')
# Erstellen eines Konto-Objekts
konto1 = Konto('Alice', 1000.00)
# Erstellen einer zweiten Referenz auf dasselbe Konto-Objekt
konto2 = konto1
# Einzahlen über die erste Referenz
konto1.einzahlen(500.00)
# Anzeigen des Kontostands über beide Referenzen
konto1.zeige_info() # Ausgabe: Inhaber: Alice, Kontostand: 1500.00 EUR
konto2.zeige_info() # Ausgabe: Inhaber: Alice, Kontostand: 1500.00 EUR
# Abheben über die zweite Referenz
konto2.abheben(200.00)
# Anzeigen des Kontostands über beide Referenzen
konto1.zeige_info() # Ausgabe: Inhaber: Alice, Kontostand: 1300.00 EUR
konto2.zeige_info() # Ausgabe: Inhaber: Alice, Kontostand: 1300.00 EURHier sind einige Szenarien, in denen diese Art von Verwirrung auftreten kann:
Unbeabsichtigte Nebenwirkungen: Änderungen, die über eine Referenz vorgenommen werden, beeinflussen hinterrücks auch die andere Referenz. Wenn man dies nicht erwartet, kann dies zu Fehler führen, die sich schwer nachvollziehen lassen.
Fehlersuche und Debugging: Es kann schwierig sein, den Ursprung von Änderungen zu identifizieren, da mehrere Referenzen auf dasselbe Objekt zugreifen und es ändern können.
Komplexe Datenstrukturen: In komplexeren Programmen mit verschachtelten Datenstrukturen kann das Verfolgen von Referenzen noch schwieriger sein.
Unveränderbare (immutable
) Python-Objekte wie Zahlen und
Strings können nicht direkt geändert werden. Stattdessen wird bei jeder
Änderung ein neues Objekt erzeugt. Dadurch merkt man oft nicht, dass
Python mit Referenzen arbeitet, da Änderungen scheinbar lokal auf die
jeweilige Variable beschränkt bleiben.
zahl1 = 10
zahl2 = zahl1
zahl2 += 5
print(zahl1) # Ausgabe: 10
print(zahl2) # Ausgabe: 15Hier verweisen zahl1 und zahl2 zunächst auf
dasselbe Integer-Objekt 10. Wenn zahl2 jedoch
geändert wird (zahl2 += 5), nimmt Python für das Ergebnis
ein anderes Integer-Objekt 15 und lässt zahl2
nun auf dieses andere Objekt verweisen, während zahl1
weiterhin auf 10 verweist.
Strings sind ebenfalls unveränderbar. Jede Änderung an einem String erzeugt ein neues String-Objekt.
string1 = 'Hallo'
string2 = string1
string2 += ', Welt!'
print(string1) # Ausgabe: Hallo
print(string2) # Ausgabe: Hallo, Welt!Hier verweist string1 auf ein String-Objekt mit dem
Inhalt Hallo
und string2 auf dasselbe Objekt. Wenn
string2 geändert wird, erzeugt Python ein neues
String-Objekt Hallo, Welt!
und string2 verweist nun
auf dieses neue Objekt, während string1 weiterhin auf
Hallo
verweist.
Unveränderbare Objekte machen einige Arten von Fehlern (siehe oben
unter Verwirrungsgefahr
) unmöglich. Deshalb schreibt man oft auch
eigene Klassen so, dass sie nie Veränderungen an einer einmal gebauten
Instanz vornehmen.
NoneIn Python ist None ein spezieller Wert, der verwendet
wird, um das Fehlen eines konkreten Wertes zu kennzeichnen. (Pythons
None entspricht in C und C++ der Nullzeiger.) Es ist ein
einzelnes Objekt, das oft verwendet wird, um Variablen zu initialisieren
oder anzuzeigen, dass eine Angabe fehlt oder dass eine Funktion nichts
zurückgibt. Zum Beispiel gibt eine Funktion, die keinen
return-Befehl enthält, automatisch None
zurück. None ist auch nützlich, wenn Sie eine Variable
anlegen wollen, die später einen Wert erhalten soll, aber vorerst leer
ist.
Oft missverstanden: Eine Nullreferenz ist nicht etwas wie
die Zahl 0 oder die leere Zeichenkette ''. Die
Zahl 0 hat den Typ int und die leere Zeichenkette hat den
Typ str, also niemals den Wert None. Die
Nullreferenz ist vielmehr die Abwesenheit von konkreten Informationen. 0
Euro zu verdienen, heißt zu wissen, was man verdient (nämlich nichts).
Ein Passwort, das keine Zeichen enthält, ist ein konkretes (wenn auch
schlechtes) Passwort.
Wenn eine Variable wahlweise den Wert None haben kann,
können Sie dies mit einem Type Hint der Art float | None
darstellen. Wenn eine Funktion nichts zurückgeben soll, kann dies mit
-> None angegeben werden. Zum Beispiel:
def beispiel_function(wert: float | None) -> None:
if wert is None:
print('Kein Wert angegeben')
else:
print(f'Der Wert ist {wert}')Das logische Gegenteil von wert is None ist
wert is not None, was für an C Gewohnte falsch aussieht.
Die Schreibweisen wert == None bzw.
wert != None werden nicht empfohlen, weil sie in
ungewöhnlich programmierten Klassen überraschende Resultate haben
können.
Nullreferenzen wie None hat deren Erfinder Tony Hoare
später als seinen Milliarden-Dollar-Fehler
bezeichnet, da die Einführung von Nullreferenzen in Programmiersprachen
zu einer Vielzahl von Programmfehlern geführt habe, die in Summe
Milliarden von Dollar gekosten hätten. Nullreferenzen verursachen häufig
Programmabstürze und Sicherheitslücken, weil oft vergessen wird,
ausreichend zu prüfen, ob eine Variable (je nach Programmiersprache)
None, NULL, nullptr,
null ist, bevor man darauf zugreift. Inzwischen bieten
viele Programmiersprachen besondere Sicherheitsmechanismen für
Nullreferenzen oder sogar Alternativen zu Nullreferenzen. In Python sind
vor allem die Type Hints zu nennen, die angeben können, dass
None nicht als Eingabe oder Ergebnis erlaubt ist. Wenn es
laut Programmcode trotzdem auftreten könnte, warnt dann die Typprüfung
davor.
In Python gibt es zwei Möglichkeiten, Objekte zu vergleichen: den
Operator == und den Operator is. Obwohl sie
ähnlich aussehen, prüfen sie völlig unterschiedliche Dinge.
== (Gleichheit) prüft, ob zwei
Objekte den gleichen Wert haben. Dabei wird die Methode
__eq__ verwendet, die in den jeweiligen Klassen
überschrieben werden kann. Beispiel:
a = [1, 2, 3]
b = [1, 2, 3]
a == b # True – gleiche Inhalteis (Identität) prüft, ob zwei
Variablen auf genau dasselbe Objekt im Speicher verweisen. Beispiel:
a = [1, 2, 3]
b = [1, 2, 3]
a is b # False – zwei verschiedene Listen, obwohl inhaltlich gleichObendrein kann man prüfen, ob ein Objekt von einer bestimmten Klasse
ist. Dazu dient die Funktion isinstance:
isinstance('Hallo', str) # True
isinstance(5, int) # True
isinstance(3.14, int) # FalseWeil jede Unterklasse perfekt ihre Oberklasse vertreten können muss,
wird isinstance auch True, wenn man nach einer
(Ober-)Oberklasse der Instanz fragt.
OOP bringt einige Vokabeln mit sich, die in der Fachsprache der Informatik völlig andere Bedeutungen haben als im Alltag. Im Laufe des Semesters sollten Sie lernen, die folgenden Vokabeln selbst zu verwenden:
class, Objekt, Instanz, instanziierenself__init__ (strenggenommen kein
Konstruktor)super()@staticmethod,
@classmethod