31. Klassen

Die Grammatik haben wir bisher als ein Dictionary dargestellt mit einer separaten Funktion zum Einlesen.

Das ist problematisch:

  • Unsere Zugiffsmethode basiert fest auf der Implementation als Dictionary von Listen. Was, wenn wir das mal ändern wollen?
  • Wenn wir die Implementation ändern, wird die Funktion zum Einlesen vielleicht vergessen und funktioniert dann nicht mehr.
  • Wenn wir mehr Sachen mit der Grammatik machen wollen (etwa Plausibilitätsprüfung, Ausgabe, Speichern), haben wir immer mehr verstreute Funktionen

Abhilfe: Zusammenfassen von Daten und Methoden zu einer Klasse – ähnlich der bekannten String, Dictionary usf.

Bevor wir uns an die doch recht aufwändige Grammatik-Klasse machen, wollen wir zunächst nochmal überlegen, was eigentlich Klassen sind. Eine Klasse ist sozusagen eine Schablone für Objekte; letztere gehen durch instanzieren aus Klassen hervor. Die Klasse sagt, was ein Objekt kann (welche Methoden es hat), und sie bestimmt auch weitgehend, was die Identität des Objekts ausmacht (welche Daten nötig sind, um ihm die Identität zu geben). Die verschiedenen Objekte (man spricht auch gern von Instanzen der Klasse) haben dann konkrete Werte für diese Daten, haben aber alle das gleiche (durch die Klasse bestimmte) Verhalten. Diese konkreten Werte bezeichnet man auch gern als Zustand des Objekts. Eine schöne Art, diese Sachverhalte auszudrücken ist: “Objekte bestehen aus Zustand und Verhalten”.

Ein ganz einfaches Beispiel ist eine Klasse, die einfach eine Identität bekommt (hier einen Namen). Sie soll zunächst nicht mehr können als piepen und pupen.

class Tiny:

  def __init__(self, myName):
    self.myName = myName

  def beep(self):
    print self.myName, "beeps"

  def poop(self):
    print self.myName, "poops"

Wenn das in tiny.py gespeichert ist, kann man folgendes tun:

>>> from tiny import Tiny
>>> t1, t2 = Tiny("Parrot"), Tiny("Rabbit")
>>> t1.beep()
Parrot beeps
>>> t2.poop()
Rabbit poops

Zumindest strengt dieses Spiel intellektuell nicht sonderlich an.

Was passiert hier?

  • Die Definition einer Klasse wird mit dem reservierten Wort class eingeleitet, danach kommt der Name der neuen Klasse (sollte mit einem Großbuchstaben anfangen), ein Doppelpunkt und wieder ein compound statement (Einrückung!)
  • Im compound statement werden dann Funktionen definiert, eben unsere Methoden. Sie alle haben ein festes erstes Argument, konventionell self. Python trägt darin beim Aufruf eine Referenz auf die Instanz ein, also auf das konkrete Objekt, auf das die Methode wirken soll. Wird etwa a.foo() aufgerufen und ist a eine Instanz einer Klasse A mit einer Methode foo(self), so übergibt Python eben a als self.
  • Der Name der ersten Methode, __init__, sieht nicht nur komisch aus, er hat auch eine spezielle Funktion: Er ist ein Konstruktor, eine Methode, die beim Erzeugen eines Objekts dieser Klasse (dem Instanzieren) aufgerufen wird. In ihr werden die benötigten Datenstrukturen angelegt und ggf. Arbeit erledigt, die vor der Benutzung des Objekts gemacht werden muss. Die speziellen Python-Namen (wir hatten ja schon __name__ gesehen) spielen bei Klassen eine sehr große Rolle, weil sie uns erlauben werden, das Verhalten der Objekte sehr weitgehend zu beeinflussen.
  • In diesem Fall muss der Konstruktor nicht viel tun – er merkt sich lediglich seinen Namen – mehr Identität hat unser einfaches Spielzeug nicht. Er schreibt dazu einen Namen in den Namespace des Objekts self, also sozusagen in sich selbst. Da jede Methode dieses self wieder übergeben bekommt, kann auch in allen Methoden auf diese Daten zugegriffen werden.
  • Das funktioniert allgemein so: Jedes Objekt hat einen eigenen Namespace. Namen darin heißen Attribute des Objekts. Wenn diese Attribute Variablen sind, heißen sie speziell Instanzvariable – das betont nochmal, dass sie zur Instanz und nicht zur Klasse gehören. In diesem Namespace stehen aber auch die Namen der Methoden (die in der Regel aus der Klasse übernommen werden).
  • Die Methodenaufrufe sehen jetzt genau aus wie von den “eingebauten” Objekten gewohnt. Beachtet nochmal, dass ihr das erste Argument der Methoden nicht übergebt, weil es Python für euch ausfüllt.

Übungen zu diesem Abschnitt

Ihr solltet euch wenigstens an den rötlich unterlegten Aufgaben versuchen

(1)

Fügt der Klasse Tiny noch eine Methode getName hinzu, die den Namen der Instanz zurückgibt. Das sollte etwa so funktionieren:

>>> from tiny import Tiny
>>> t1, t2 = Tiny("Parrot"), Tiny("Rabbit")
>>> t1.getName()
'Parrot'

(2)

Die Plüschtiere, die Tiny modelliert, sollen jetzt nicht nur einen Namen haben, sondern auch noch satt und hungrig sein können. Am Anfang soll die Instanzenvariable hunger (die analog zu myName funktioniert) dabei Null sein, danach können die Methoden eat und wait den Hunger jeweils um eins reduzieren oder erhöhen. Verändert die beep-Methode, so dass sie, wenn das Plüschtier Hunger hat (also self.hunger>0 gilt), ihrer Ausgabe ein “Hungry” voranstellt.

(3)

Überlegt euch, wie ihr die Funktionalität der Klasse der letzten Aufgabe ohne Klassen hinbekommt, also nur mit Funktionen und Variablen, die zwischen ihnen hin- und hergeschoben werden.

(4)

Überlegt euch, was alles nötig wäre, um aus der Tiny-Klasse einen Tamagotchi zu bauen. Was davon könnt ihr schon, was nicht?


Markus Demleitner

Copyright Notice