BICS
Informatik Sek II
Objektorientiertes Programmieren

Python
LFB.-Kurs
W. Arnhold
[ Übersicht | vorige Seite | nächste Seite | Ganz unten ]
Python Python - Syntax :  Klassen  I

Klassen

Wir steigen nun ein in einen Bereich der Softwaretechnik, der als "Objektorientierte Programmierung" bezeichnet wird. Um es gleich vorweg zu sagen: Python liefert in dieser Abteilung allerhand, was "große" Programmiersprachen auch können, aber nicht alles. Für unseren Bedarf aber reicht es.

Was ist nun eine Klasse? Als Antwort eine

Definition
Eine Klasse ist die Zusammenfassung eines Datenobjekts mit den darauf möglichen Zugriffen zu einer Einheit.

Was ist ein Datenobjekt? Nun, es ist die Zusammenfassung aller Speicherplätze, die dazu dienen, einen Gegenstand der Wirklichkeit in der Programmiersprache abzubilden.

Was sind Zugriffe? Das sind im Allgemeinen alle Methoden, mit denen man die internen Speicherplätze der Klasse lesen oder verändern (schreiben) kann.

Auch wenn wir noch hineinschauen können, betrachten wir die Klasse von nun an quasi wie eine Blackbox, von der wir nur die äußeren Bedienknöpfe sehen können. Das heißt, dass wir auf die Daten von außen nur über die Zugriffsprozeduren (am besten nur die dokumentierten) zugreifen.

Noch etwas zur Terminologie: in objektorientierten Sprachen ist ein etwas anderer Sprachgebrauch üblich:

  • Wenn man ein konkretes Objekt einer Klasse erzeugt, so spricht man von einem Exemplar, bzw. einer Instanz dieser Klasse.
  • Wenn man dieses Exemplar zu etwas veranlassen will, wird gesagt, man sende ihm eine Botschaft . Dies geschieht, indem man eine Klassenmethode aufruft. Diese heißen auch Prozeduren .
Sie müssen diese Begriffe nicht unbedingt benutzen, wenn Sie aber als schlau in Informatik gelten wollen, sollten Sie sie kennen.

So, genug der Vorrede! Ein Beispiel muss her! Wir nehmen etwas einfaches, das zugleich den Vorteil hat, zu den klassischen Datenobjekten der Informatik zu gehören: den Stack (Stapel). Einen Stack können Sie sich etwa # vorstellen wie einen Ablagekasten im Büro, in den Briefe hineinkommen, die beantwortet werden müssen. Neue Briefe kommen immer oben auf den Stack und die zu beantwortenden Briefe werden auch immer nur von oben genommen. Damit werden sie aus dem Stapel entfernt. Manchmal will man aber nur sehen, was oben auf dem Stapel liegt. In diesem Fall bleibt der Brief auf dem Stapel. Außerdem kann man ja nur Briefe entnehmen, wenn der Stapel nicht leer ist. Das kann man aber prüfen.

Somit ist ein Stack ein Objekt, das es gestattet, Dinge aufzuheben. Wie dies geschieht, ist nebensächlich. Die Zugriffe sind also (ich verwende hier die englischen Bezeichnungen, weil sie in der Informatik gebräuchlich sind):

empty:
(leer) liefert einen Wahrheitswert (ist der Stapel leer, ja oder nein?)
push :
(stoßen) ein zu speicherndes Objekt (ein Brief) wird oben auf den Stapel gelegt.
pop :
(herausspringen) Das oberste Objekt (ein Brief) wird dem Stapel entnommen. Dies geht nur, wenn es ein solches Objekt gibt, der Stapel also nicht leer ist.
top :
(oben) Eine Kopie des obersten Objektes (der Brief wird fotokopiert) wird geliefert. Dies geht nur, wenn es ein solches Objekt gibt, der Stapel also nicht leer ist.#
Das war es schon. So seltsam es sich anhört, aber diese kleine Datenstruktur ist ausgesprochen nützlich. Wir definieren nun (mit viel Kommentar) eine Klasse, die diesen Stack realisiert:
class Stack:
	def __init__(self):
		self.l = []
	def empty(self):
		return len(self.l) == 0
	def push(self, wert):
		self.l.insert(0, wert)
	def pop(self):
		wert = self.l[0]
		del self.l[0]
		return wert
	def top(self):
		return self.l[0]
Das "class"und der Doppelpunkt sind vorgeschrieben, wenn man eine Klasse definiert. "Stack" ist der Klassenname. Es geht (wie gewohnt) eingerückt weiter. Alles was auf dieser Einrückungsstufe steht, gehört zur Klasse.

Die wichtigste Klassenprozedur? Eine Prozedur, die diesen speziellen Namen "__init__" hat, wird automatisch immer dann ausgeführt, wenn man ein neues Klassenobjekt (eine Instanz, ein Exemplar) erzeugt. Eine solche Prozedur wird Konstruktor genannt. Sie muss, wie alle Klassenprozeduren (Methoden), als ersten Parameter ein Objekt namens "self" besitzen. Dies ist ein Verweis auf sich selbst. Wir werden später sehen, wozu das gut ist.
Weiter noch mal eingerückt, jetzt kommen die Prozeduranweisungen (Methoden-Implementation).

Damit haben wir ein Objekt "l" erzeugt und es im Datenraum der Klasse angelegt. Es existiert damit solange, wie die Klasse existiert. Es soll eine leere Liste sein (irgenwie müssen wir unsere Werte ja speichern).

Die nächste Klassenprozedur, "empty".
Sie soll prüfen ob der Stack leer ist. Sie bekommt wieder den Parameter "self" mit, damit innerhalb der Prozedur auf den Datenbestand der Klasse zugegriffen werden kann.

Die Prüfung geschieht, indem die Länge der Liste im eigenen Datenbereich ("self.l") mit dem Wert 0 verglichen wird. Das Prüfergebnis wird zurückgegeben.

Diese Prozedur "push" soll also den Parameter "wert" oben auf den Stapel legen.

Dies wird realisiert, indem die "eingebaute" Listenmethode "insert" (einfügen) aufgerufen wird, die den Wert an der Stelle 0 einfügt.

Die Prozedur "pop" soll den obersten Listenwert (Brief) entnehmen.

Das Listenelement an der Position 0 wird in die lokale (nur innerhalb dieser Prozedur existierende) Variable "wert" kopiert.
Anschließend wird das erste Listenelement gelöscht. Die weiteren Listenelemente rücken nach. Da wir wissen, dass Listen keine wirklichen Datenobjekte, sondern nur Verweise enthalten, müssten wir sagen: Der Verweis auf das erste Listenelement wird entfernt. Das Element selbst wird nicht gelöscht, da noch ein Verweis darauf in "wert" existiert.

Dieser Verweis wird nun zurückgegeben. Somit erhält der Aufrufer dieser Klassenprozedur (Methode) den Verweis und damit das erste Objekt.

Hiermit, "top", soll das erste Element herausgereicht werden, ohne dass es entfernt wird.
Nur der Verweis wird herausgereicht.

Damit ist unsere Klasse fertig.

Was heisst das genau? Haben wir jetzt einen Stack? Nein ! ! !
Was haben wir dann? Wir haben dem Interpreter eine Bauanleitung gegeben, wie ein Stack auszusehen hat. Wenn wir einen benötigen, brauchen wir uns jetzt nur noch auf diese Bauanleitung zu beziehen. Das könnte so aussehen:

schreibtisch = Stack()
So sieht das aus. Jetzt haben wir einen Stack mit Namen "schreibtisch". Die runden Klammern hinter dem Klassennamen deuten den Aufruf des Konstruktors an. Alle Anweisungen in "__init__" sind jetzt ausgeführt, der Stack ist gebrauchsfertig. Jetzt beobachten wir die Klasse bei der Arbeit. Ihre Methoden werden beim Aufruf immer mit einem Punkt an den Objektnamen angehängt.
print 'Was ich noch zu tun habe:'
print 'Bitte Arbeiten am Schreibtisch eingeben (oder "ende")'
while 1:
	arbeit = raw_input()
	if arbeit == 'ende':
		break
	schreibtisch.push(arbeit)
#  Objektname^      ^  ^    ^
#  Punkt------------|  |    |
#  Methodenname--------|    |
#  Methodenparameter--------|
Erstaunlich dabei ist, dass die Prozedur nur mit einem Parameter aufgerufen wird, obwohl sie mit zweien definiert wurde. Lassen Sie sich davon nicht verwirren: Beim Aufruf wird der Parameter "self" automatisch vom Interpreter mit hineingefüttert, Sie brauchen ihn normalerweise nie mit anzugeben.
print
if schreibtisch.empty():
# Deshalb ist hier----^ auch kein Parameter zu sehen!
	print 'Wie schön, nichts zu tun!'
else:
	print 'Zuerst zu erledigen: %s' % schreibtisch.top()
print
print 'In dieser Reihenfolge:'
while not schreibtisch.empty():
	print '\tAbarbeiten: %s' % schreibtisch.pop()
Beachten Sie bitte: vor einem lesenden Zugriff wird immer erst geprüft, ob der Stack leer ist.

[ Übersicht | vorige Seite | nächste Seite | Ganz oben ]

©   W. Arnhold Oktober 2000