Klassen
Klassen sind ein Konstrukt in Python, mit dem wir eigene, zusammengesetzte Datentypen schreiben können. Wir erinnern uns: Um einen einen Datentyp zu definieren müssen wir festlegen, welche Werte repräsentiert werden können (die Daten) und welche Operationen darauf definiert sind.
Wir führen Klassen anhand eines Beispiels ein. Wir nehmen an, dass wir eine Zeichenanwendung schreiben und wir dafür die x und y Koordinaten in einem Datentyp Punkt
speichern wollen. Die x und y Koordinate entspricht unseren Daten. Ausserdem wollen wir einen
Punkt auf der Ebene um eine angegebene Strecke verschieben können. Dies entspricht der Operation, die wir
auf den Daten ausführen können.
Definition der Klasse
Um den Datentyp zu definieren schreiben wir das Schlüsselwort class
gefolgt von einem von uns gewählten
Namen (hier Point
) und abgeschlossen durch den Doppelpunkt. Alle Definitionen, die wir für diese Klasse vornehmen, werden innerhalb des sogenannten
Klassenrumpfs definiert. Dieser wird durch Einrücken markiert.
class Point:
pass
Da wir an dieser Stelle noch keine Daten oder Operationen definiert haben, schreiben wir in den Klassenrumpf einfach das Schlüsselwort pass
welches für eine leere Operation steht, die gar nichts macht. Später ersetzen wir pass
dann mit unseren Definitionen.
Erzeugen von Instanzen
Obwohl unsere Klasse Point
noch leer ist, können wir bereits Instanzen davon erzeugen. Wir erzeugen eine Instanz mit dem folgende Aufruf:
aPoint = Point()
Mit dem Aufruf Point()
erzeugen wir die neue Instanz. Dieser geben wir dann den Namen aPoint
.
Wir können natürlich auch weitere Instanzen erzeugen. Das folgende Beispiel erzeugt 3 Instanzen der Klasse
Point
und gibt jeder Instanz einen eigenen Namen. Über diesen Namen können wir dann auf die
Daten und Operationen zugreifen.
point1 = Point()
point2 = Point()
point3 = Point()
Häufig werden Instanzen von Klassen auch Objekte genannt.
Attribute
Die oben definierten Punktobjekte sind noch sehr langweilig, da diese noch gar keine Daten repräsentieren.
Deshalb erweitern wir nun unsere Definition der Klasse so, dass auch Daten gespeichert werden können.
Dazu definieren wir uns innerhalb des Klassenrumpfs die spezielle Funktion __init__
.
class Point:
def __init__(self):
self.x = 0
self.y = 0
Diese __init__
Funktion nimmt ein Argument entgegen, welches wir typischerweise self
nennen. self
repräsentiert die Instanz der Klasse. Auf dieser Instanz können wir nun unsere Daten definieren. In unserem Fall sind das die x und y Koordinaten, welche wir beide
mit 0 initialisieren. Wir nennen die so auf der Klasse definierten Daten Attribute.
Es fällt Ihnen sicher die Ähnlichkeit mit normalen Variablendefinition auf. Der einzige Unterschied ist, dass wir nicht x = 0
sonder self.x = 0
schreiben. Dieser Unterschied deutet darauf hin, dass
die Attribute zur Instanz der Klasse gehören, die ja durch self
repräsentiert ist.
Wenn wir nun mit
aPoint = Point()
eine neue Instanz der Klasse erzeugen, dann wird von Python automatisch die Funktion __init__
aufgerufen und der Code, der im Rumpf von __init__
definiert ist, wird ausgeführt.
Setzen eigener Werte für die Attribute
Wir können uns nun beliebig viele Instanzen der Klasse Punkt erstellen. Diese repräsentieren aber noch
immer alle den Punkt , da die Attribute x
und y
immer auf 0 gesetzt werden.
Wir brauchen also eine Möglichkeit um die Attribute verschieden zu initialisieren. Dies machen wir, indem
wir der __init__
Funktion weitere Argumente angeben:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
In der obigen Definition werden zwei Argumente, nämlich x
und y
entgegengenommen. Weil die Funktion nun neben
self
zwei weitere Argumente nimmt, müssen wir bei der Erzeugung der Klasse auch zwei Werte übergeben.
point1 = Point(1, 3)
point2 = Point(3, 4)
Im obigen Aufruf haben wir zwei Punktobjekte erstellt. Das erste repräsentiert den Punkt und das zweite den Punkt .
Wir können auf die Attribute der Klasse auch von aussen zugreifen. Dafür schreiben wir den Namen der Instanz, auf dem das Attribut definiert ist, und, durch einen Punkt getrennt, den Attributnamen:
point1 = Point(1, 3)
print("Die x-Koordinate von point1 ist: ", point1.x)
print("Die y-Koordinate von point1 ist: ", point1.y)
Am besten Sie probieren die gleich selbst aus:
Experimente:
- Erzeugen Sie einen zweiten Punkt mit anderen Koordinaten. Geben Sie auch diese aus.
- Fügen Sie eine print Anweisung in die init-Funktion ein, damit Sie sehen, dass diese auch tatsächlich aufgerufen wird.
- Können Sie die Attribute von aussen verändern, indem Sie diesen einen neuen Wert zuweisen?
Einschub: Klassen und Dictionaries
Im Artikel zum Thema Dictionaries haben wir gesehen, dass wir solche zusammengesetzten Daten auch gut mit Dictionaries umsetzen können. Wir hätten in diesem Fall einfach etwa folgendes geschrieben:
aPoint = {'x' : 7, 'y' : 15}
Um auf die Elemente zuzugreifen, können wir die normale Syntax für Dictionaries verwenden:
aPoint['x']
aPoint['y']
Was ist nun der Vorteil von der Klasse?
Diese Frage ist sehr berechtigt. In der Tat könnten wir gut ohne Klassen leben. Klassen bieten aber trotzdem viele Vorteile, und sind inzwischen in den meisten Programmiersprachen standard. Der Grund ist, dass wir im Gegensatz zu Dictionaries einen neuen Datentyp einführen. Dies erlaubt der Laufzeitumgebung weitere Überprüfungen zu machen, Fehler zu erkennen und bessere Fehlermeldungen zu generieren. Viel wichtiger ist aber, dass wir dadurch auch die Möglichkeit bekommen, Operationen, die auf den Daten definiert sind, zu deklarieren.
Methoden
Angenommen, wir wollen unserem Datentyp Point
eine Operation zur Verfügung stellen,
um den Punkt um die Strecke dx
in x-Richtung und dy
in y-Richtung zu verschieben. Traditionell würden wir dies als Funktion wie folgt schreiben:
def translate(aPoint, dx, dy):
newx = aPoint.x + dx
newy = aPoint.y + dy
return Point(newx, newy)
Als Argument bekommt die Funktion eine Instanz der Klasse Punkt (genannt aPoint
)
und extrahiert dessen Felder. Um die Funktion aufzurufen, würden wir Folgendes schreiben:
aPoint = Point(0, 0)
translatedPoint = translate(aPoint, 10, 20)
Dank Klassen können wir diese Funktionalität direkt auf der Klasse definieren. Dies ist im folgenden Code dargestellt:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def translate(self, dx, dy):
newx = self.x + dx
newy = self.y + dy
return Point(newx, newy)
Wir sehen, dass translate
hier fast wie die obige traditionelle Funktion definiert ist. Nur fällt uns hier wieder das zusätzliche self
Argument auf. Dies erlaubt uns, die Funktion mit einer speziellen Syntax aufzurufen: Wir schreiben wir wie bei den Attributen den Namen der Instanz und, durch Punkt getrennt, den Funktionsname:
aPoint = Point(2, 3)
aPoint.translate(10, 20)
Bei diesem Aufruf übergibt Python die Instanz der Klasse Punkt auf der wir die Funktion aufrufen, (also aPoint
) implizit an das Argument self
. Es wird hiermit klarer, dass die Funktion translate
auf einer Instanz vom DatenTyp Point
arbeitet. Es ist nicht möglich, diese mit einem falschen Argument aufzurufen, wie dies mit herkömmlichen Funktionen leicht passieren könnte.
Solche, innerhalb der Klasse definierten Funktionen, werden Methoden genannt.
Merke: Das erste Argument einer Methode hat immer den Namen
self
. Python übergibt an dieses Argument implizit die Instanz auf welcher es aufgerufen wurde. Es übergibt also das aufrufende Objekt selbst.
Am besten Sie probieren alle Konzepte gleich selbst im Code aus:
Experimente
- Schreiben Sie für in der Klasse
Point
eine Methodeprint
welche die selbe Art Ausgabe erzeugt wie schon im Code verwendet wird. Ersetzen Sie dann die Ausgaben mit Hilfe der Methodeprint
. - Fügen Sie der Klasse
Point
noch ein Attributlabel
hinzu um Punkte benennen zu können. Ergänzen Sie die Methodeprint
so dass das Label mit ausgegeben wird. Erstellen Sie dann zwei Punktehideout
undtreasure
mit entsprechendem Label und geben Sie diese auf die Konsole aus.
Fragen und Kommentare
Haben Sie Fragen oder Kommentare zu diesem Artikel? Nutzen Sie das Forum und helfen Sie sich und Ihren Mitstudierenden dieses Thema besser zu verstehen.