Skip to main content

Klassen

Klassen sind ein Konstrukt in Python um eigene, zusammengesetzte Datentypen zu schreiben. Wir erinnern uns, dass wir um einen Datentyp zu definieren festlegen müssen, 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 mit dem wir darauf zugreifen können. .

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 (0,0)(0, 0), 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 nun zwei Punktobjekte erstellt. Das erste repräsentiert den Punkt (1,3)(1, 3) und das zweite den Punkt (3,4)(3, 4).

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 bereits 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 dann auf die Elemente wie folgt zugreifen zu können.

aPoint['x']
aPoint['y']

Was ist nun der Vorteil von der Klasse?

In der Tat könnten wir gut ohne Klassen leben. Klassen bieten aber trotzdem viele Vorteile, und sind inzwischen in den allermeisten 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 (hier 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 nun 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 übergit 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 und es ist nicht möglich, diese mit einem falschen Argument aufzurufen, wie dies mit herkömmlichen Funktionen möglich gewesen wäre.

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 Methode print welche die selbe Art Ausgabe erzeugt wie schon im Code verwendet wird. Ersetzen Sie dann die Ausgaben mit Hilfe der Methode print.
  • Fügen Sie der Klasse Point noch ein Attribut label hinzu um Punkte benennen zu können. Ergänzen Sie die Methode print so dass das Label mit ausgegeben wird. Erstellen Sie dann zwei Punkte hideout und treasure 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.