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
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