Lektion 4

Klassen

Wenn man komplizierte Programme schreibt, braucht man einen Mechanismus, um die Objekte mit denen man arbeiten möchte, besser im Programm abzubilden, als das mit den Datentypen, die wir bisher kennengelernt haben, möglich ist. Insbesondere ist es nötig, eigene Datentypen definieren zu können.

Dazu gibt es in vielen Programmiersprachen, so auch in Python, das Konzept der Objektorientierung. Man erzeugt neue Datentypen als ein Paket von Eigenschaften (Attributen), auf die eine Reihe von Funktionen (Methoden) angewendet werden können. Die Zusammenfassung von Attributen und Methoden definiert eine Klasse, die als Bauplan dient um verschiedene Objekte dieses Typs zu erzeugen, die sich dann ggf. in den Werten der Attribute unterscheiden.

In Python ist praktisch alles ein Objekt, wir haben also schon reichlich mit Objekten zu tun gehabt. Insbesondere sind Listen auch Objekte, allerdings haben sie ein wenig besondere Syntax, um die Verwendung leichter zu machen. Wir haben folgende Syntax kennengelernt, um eine neue Liste zu erstellen:

l = []

Äquivalent kann man auch explizit schreiben, dass man ein neues Objekt der Klasse list erzeugen möchte:

l = list()

Listen haben keine für den Benutzer sichtbaren Attribute, verfügen aber über zahlreiche Methoden, wie etwa append. Methoden (und auch Attribute) werden über die Punktnotation aufgerufen:

l.append(5)

Die einfachste Klasse, die man schreiben kann, ist die leere Klasse:

class student(object):
    pass

pass ist das leere Statement, das keine Effekte hat. Man kann jetzt neue Objekte der Klasse erstellen und Attribute setzen

daneel = student()
daneel.matrikel = "1747526"
daneel.alter = 23

Die Attribute des Objekts verhalten sich wie Variablen, die lokal an das Objekt gebunden sind. Wie gewöhnliche Variablen werden sie erzeugt, wenn das erste Mal schreibend darauf zugegriffen wird und es ist verboten vorher lesend zuzugreifen. Für selbstgeschriebene Objekte gilt das gleiche wie für Listen oder Wörterbücher: Zuweisungen an Variablen erzeugen keine Kopien.

Eine Methode ist ein Attribut der Klasse, das eine besondere Art von Funktion als Wert hat. Methoden werden so notiert:

class foo(object):
    def bar(self):
        print "bar"

f = foo()
f.bar()

Man beachte, dass bei Methodenaufrufen das self Argument weggelassen wird. In der Methode hat self stets das Objekt auf dem die Methode aufgerufen wird als Wert. Es wird beim Methodenaufruf automatisch übergeben. Über self kann man also innerhalb der Methode auf die Attribute des Objekts zugreifen, neue Attribute erzeugen und Methoden des Objekts aufrufen.

class foo(object):
    def bar(self):
        self.a = 42

f = foo()
f.a=23  
f.bar()
print f.a

In der Regel möchte man, dass alle Objekte einer Klasse bereits bei der Erzeugung eine Reihe von Attributen haben, ggf. bereits mit Werten. In Python wird das durch die besondere Methode __init__ erreicht. Sie wird, so sie existiert, aufgerufen, wenn das Objekt erzeugt wird.

class foo(object):
    def __init__(self):
        print "init aufgerufen"
        self.a = 42
        self.b = 23

f = foo()
print f.a

__init__ kann auch mehr Argumente nehmen. Sie werden dann bei der Objekterzeugung übergeben.

class student(object):
    def __init__(self, name, matrikel):
        self.name = name
        self.matrikel = matrikel

daneel = student("R. Daneel Olivaw","17456327")
giskard = student("R. Giskard Reventlov","10568432")

print daneel.name, giskard.name

In der Regel ist es eine gute Idee, nicht direkt auf die Attribute zuzugreifen, sondern stattdessen Methoden für den Zugriff zu schreiben. So ist es später leichter Veränderungen an der Klasse vorzunehmen.

Prinzipiell könnte alles, was wir bisher über Klassen wissen auch relativ problemlos mit Funktionen und Wörterbüchern realisieren. Der große Vorteil von Klassen ist, dass sie Vererbung unterstützen. Vererbung realisiert ein ist ein Verhältnis zwischen Klassen. So kann man Methoden und Eigenschaften anderer Klassen übernehmen und erweitern, ohne den Code kopieren zu müssen. Die nötige Syntax haben wir bereits gesehen: in den obigen Beispielen erbt die Klasse foo von der Klasse object.

class auto(object):
    def fahren(self):
        print "fahre"

class eismann(auto):
    def bimmeln(self):
        print "klingeling"

e = eismann()
e.fahren()
e.bimmeln()

Definiert eine Unterklasse eine Methode die bereits in der Oberklasse vorkommt, wird die Methode der Oberklasse überschrieben.

class eismann(auto):
    def bimmeln(self):
        print "klingeling"

    def fahren(self):
        print "vroom"

e = eismann()
e.fahren() # vroom

Beispiel

Beispielhaft wollen wir eine Klasse Stapel schreiben. Man kann Objekte auf den Stapel legen und das oberste Objekt wieder entfernen. Ein Stapel hat also zwei Methoden, ablegen und aufnehmen. Außerdem muss der Stapel sich alle Werte merken, die abgelegt wurden. Der aufmerksame Leser erkennt gleich, dass man alle diese Anforderungen bereits mit einer Liste erfüllen kann: append und pop sind analog zu ablegen und aufnehmen. Um das Beispiel spannender zu machen, werden wir jedoch auf die Verwendung einer Liste verzichten.

Ohne eine Liste zu benutzen, müssen wir uns eine andere Art überlegen, wie wir die Objekte auf dem Stapel abspeichern. Es bietet sich an, eine weitere Klasse zu schreiben, die ein Container für ein einzelnes Objekt auf dem Stapel darstellt. Das Containerobjekt hat zwei Attribute, den Wert den es speichert, und einen Verweis auf das darunterliegende Containerobjekt

class Item:
    def __init__(self,wert,naechstes):
        self.wert = wert
        self.naechstes = naechstes

    def getNaechstes(self):
        return self.naechstes

    def getWert(self):
        return self.wert

Die Stapelklasse selbst hat nur ein Attribut, das oberste Containerobjekt auf dem Stapel. Wir initialisieren es mit None für einen leeren Stapel. Die Methode ablegen ersetzt das oberste Element durch ein neues Stapelelement, die Methode aufnehmen entfernt das oberste Stapelelement.

class Stapel:
    def __init__(self):
        self.top = None

    def ablegen(self,wert):
        tmp = self.top
        self.top = Item(wert,tmp)

    def aufnehmen(self):
        if self.top == None:
            return None

        tmp = self.top.getWert()
        self.top = self.top.getNaechstes()
        return tmp