Die erste Aufgabe

Zur Kursübersicht

                                        


vorige Seite

nächste Seite

Daten in Datei abspeichern

Eine kleine Shell im Textmodus

Mit mehreren ToDo - Listen arbeiten

Quelltext zwischen Linux und Windows anpassen

Listing 1

Listing 2

Listing todo.py

Listing MultiToDo.py


eine erste class selber formulieren

Folgende Anmerkungen sind für dieses kleine Programm zu machen:

Es ist möglich, mit Python sowohl in der klassischen prozeduralen Form zu programmieren, als auch in objektorientierter Form. Wir wollen hier einfach mal mit der objektorientierten Form einsteigen. Wenn man sich Listing 1 anschaut, fallen sofort einige besondere Schlüsselwörter auf, die nachfolgend beschrieben werden:

#
Mit der "Raute" wird ein Kommentar eingeleitet

class
das typische Schlüsselwort, um eine Objekt - Klasse einzuleiten, es ist auch in einigen anderen Sprachen längst üblich. Nach "class" folgt unmittelbar der name der Klasse.

def
dies ist das Schlüsselwort, um eine Funktionsdefinition einzuleiten. Nach "def" folgt unmittelbar der Name der Funktion.

__init__
die so bezeichnete besondere Funktion erzeugt eine Instanz der beschriebenen Klasse. Sie wird indirekt aufgerufen, wenn ich nach folgendem Muster eine Instanz bilde:

Beispiel = classname(Parameterliste)

In diesem Beispielfall wird die Variable Beispiel eine Instanz der Klasse "classname".
Parameter sind dann mit anzugeben, wenn sie in der Definition der Funktion __init__( .. ) als Vorgabe enthalten sind. Der Parameter "self" gehört zur internen Organisation des Objekts und wird beim Aufruf der Funktion nicht aufgeführt.

self
Mit Hilfe von "self" wird der jeweilige aktuelle Name des Objektes verwaltet, den die gerade aktuelle Instanz des abstrakten Objektes hat. Dies wird genutzt, beim Zugriff auf Teile des Objektes von anderen Funktionen desselben Objektes aus. Z.B.

class test:
    def  __init__(self):
        self.text =  ''
    def funktion2(self):
        print  self.text

t = test()
    # nicht das Klammenpaar vergessen!
t.text = 'alles ok.'
t.funktion2()

>>> alles ok.

Was ist passiert? Die Instanz t von der Klasse test hat natürlich die Eigenschaften der Klasse mitgeerbt. Dazu gehört hier die Variable self.text, die für die Instanz t nun t.text heißt. Der Name self ist also durch den eigenen Namen der Instanz ersetzt worden. Ebenso verfügt t über die funktion2. Wird sie für t mit t.funktion2() aufgerufen, dann wird sie so ausgeführt, dass alle "selfs", die in der Funktion vorkommen, als t gelesen werden. Daher kommt t.text auch in seiner aktuellen Belegung als Ausgabe.

liste
ist ein Variablenname, der an dieser Stelle im Quelltext erstmalig auftaucht und nicht vorher deklariert werden muss, wie es in anderen Sprachen oft notwendig ist. Hier wird er als Listentyp definiert, was an der Zuweisung zu erkennen ist, denn eine Liste wird mit eckigen Klammern begrenzt. Diese Liste hier ist leer, deshalb steht nichts zwischen den Klammern.

append( )
ist eine Funktion, die sowohl für Listenvariable als auch für eine Reihe anderer Typen definiert ist. Sie gestattet das Hinzufügen eines neuen zusätzlichen Elementes, das dann als Parameter dieser Funktion anzugeben ist. Solche bereits eingebaute Funktionen, wie auch die Variablen einer class werden in der Fachliteratur als "Attribute"  bezeichnet..

liste = append(neuElement)

Wenn wir den kleinen Quelltext herauskopiert haben, speichern wir ihn in der Datei "todo.py"
Wie starten wir nun unseren Quelltext? Bisher haben wir "nur" eine kleine Klasse definiert, die den Namen
"notiz" trägt und die Funktionen "zeige( )" und "hinzu( )" zur Benutzung anbietet. Die Funktion __init( )__ wird
nicht extern aufgerufen, sondern diese läuft automatisch bei der Instanziierung ab (wie Constructor in Pascal).

Also geben wir aus dem laufenden Python - Interpreter ein:

>>> from todo import *

womit wir unserem Python alle diese Funktionen bekannt gemacht haben. Nun leiten wir uns einen eigenen Notizblock ab, den wir "n" (für Schreibfaule) nennen wollen.

>>> n = notiz()

Das sollte Python ohne Meckern schlucken. Zwischen den Klammern der Funktion steht übrigens nichts. Unser konkreter Notizblock heißt jetzt n, der nach dem Bauplan der Klasse notiz gebaut ist. In der speziellen Instanz n hat also die Variable self den Wert n.
Nun lassen wir unseren Notizblock mal arbeiten (wenn zuerst auch nur zwei kleine Funktionen vorhanden sind). Dazu rufen wir eine der vorbereiteten Funktionen auf, indem wir an den Namen unserer Instanz n mit einem Punkt gekoppelt, die Funktion zeige( ) rufen. Dies erfolgt aber mit Klammern, zwischen denen im Bedarfsfall Parameter stehen könnten. Wir hatten keine definiert, also ist der Platz zwischen den Klammern leer. Die Funktion zeige( ) hat die Aufgabe, die Klasseninterne Variable n.liste auszugeben.

>>> n.zeige()

[]
>>>

Diese Antwort verwundert nicht, denn der Notizblock ist noch leer. Man hätte dieses Ergebnis übrigens auch mit dem manuellen Befehl print n.liste erzeugen können. Aber in der objektorientierten Programmierung soll man mittels Funktionen auf die Daten der Klasse zugreifen, damit garantiert ist, dass auch nur gültige und definierte Veränderungen an unseren Daten möglich sind. Aber für Zwecke der Fehlersuche beim Programmieren zum Beispiel ist so etwas ganz legitim.

Nun schreiben wir mal was drauf, auf unseren Notizzettel. Dazu verwenden wir die vorbereitete Funktion hinzu( ), die einen Parameter erwartet, der hier ein String sein sollte. Dieser wird durch die Funktion an unsere Liste angefügt.

>>> n.hinzu('Eintrag 1')

Auch das wird kommentarlos hingenommen. Es kommt auch keine Antwort, da wir die nicht programmiert haben. Wir können diese Antwort aber herbeiführen:

>>> n.zeige()

['Eintrag 1']
>>>

Na bitte, der Zettel ist nicht mehr leer! Nun können wir immer so weiterprobieren und immer mehr hinzufügen. Wenn wir das Programm verlassen, ist alles wieder weg. Aber diese Arbeitsstufe war ja nur zum ersten Kennlernen gedacht. Es fehlt u.a. noch die wichtige Funktionen des Abspeicherns der gesamten Notizliste.

Dazu nehmen wir uns Listing 2 her.

Das Abspeichern erreichen wir, in dem wir unsere Notizliste in eine Datei auslagern. Beim Aufruf des Programms wird dann nachgesehen, ob es diese Datei schon gibt. Ist dies der Fall, dann wird sie in den Notizzettel hineingeladen. Wenn nicht, wird mit einem leeren Zettel angefangen, was sonst :-). Beim Beenden des Programms wird der Notizzettel abgespeichert. Soweit erstmal zur Idee.

Wie arbeitet man nun mit Dateien (öffnen, schreiben, lesen, schließen)?
Folgende Zeilen öffnen jeweils eine Datei:

    f = open('MyNotes', "r")

hier zum lesen (read) der Daten oder

    f = open('MyNotes', "w")

dagegen zum schreiben (write) der Daten.

Mit der Variablen f wird dann jeweils die Datei angesprochen, z.B. durch

    f.write('weiterer Text')

Nach beendeter Benutzung der Datei wird diese wieder geschlossen, wie in vielen anderen Programmiersprachen auch.

    f.close()

In unserem Beispiel wurde in die Datei jedoch nicht direkt mit write hineingeschrieben, sondern wir haben das Modul pickle benutzt. Mit der Zeile

    import pickle

haben wir es hinzugeladen. Pickle erlaubt es, Objekte wie Strings, Listen usw. bis hin zu kompletten Klassen "einzulegen", das heißt durch organisierende Steuerzeichen ergänzt,  in einen Datenstrom zu verwandeln, den wir dann in unsere Datei hineinschieben.

    pickle.dump(self.liste, f)

So ist es programmiert, zum Abspeichern der Notizliste vor Beendigung des Programms. Umgekehrt werden die Daten beim Laden aus der Datei vor der Verwendung "entpickelt" :-)

    self.liste = pickle.load(f)

Ansonsten ist alles geblieben wie beim vorigen Beispiel. Ach halt, etwas habe ich noch vergessen. Das Abspeichern beim Beenden des Programms erfolgt noch nicht automatisch (das kommt aber noch!). Wir müssen es mit der Hand ausführen. Durch den Aufruf:

    n.rette()

geschieht dies auch, sofern wir vorher unseren Notizzettel mit n = notiz( ) erzeugt hatten.

Die beiden an dieser Stelle noch nicht besprochenen Befehle try und except dienen hier dazu, einen Fehler abzufangen, der speziell beim erstmaligen Start dieses Programms auftritt, weil es ja da das File "MyNotes" noch nicht gibt. Das ändert sich, sobald die Funktion n.rette erstmalig aufgerufen wurde. Die Fehlerbehandlung ergab sich aus einen Hinweis von Juergen Scheuer, dem auffiel, dass das Modul früherer Fassung beim Erststart einen Fehler erzeugt.

Es ist ohnehin sinnvoll solche Fehler abzufangen, weil es durch Verschieben des Quelltextes ohne das File schnell mal versehentlich fehlen könnte.
Wenn unser Programm dann weiter ausgebaut wird, wird sich noch eine andere Lösungsmöglichkeit mit dem Modul os zeigen.

Noch am Rande ein anderes kleines Problem, besonders wenn man das Programm gleichzeitig unter Linux und Windows nutzt. In den Notizzeilen können natürlicherweise auch Umlaute (ÄÖÜäöü) sowie ß vorkommen. Soll eine mit Windows erzeugte Notiz unter Linux gelesen werden oder umgekehrt. gibt es da Darstellungsprobleme. Die einfachste Abhilfe wäre, diese Zeichen zu vermeiden. Etwas aufwendiger wäre es, eine spezielle Codierung für diese Zeichen zu erfinden, die das Programm dann selbst wieder auflöst. Da die neueren Versionenvon Python nun mit Unicode arbeiten, könnte das Problem damit beseitigt werden. Das schauen wir uns aber später an. :-)


Steuern unseres kleinen Moduls mit einer einfachen Bedienoberfläche

Bisher war unser kleines Beispielprogramm eigentlich nur eine Funktionssammlung, die immer mit der Hand zur Ausführung gebracht wurden. Nun wollen wir auf diese Sammlung von verschiedenen Funktionen noch eine kleine Bedienoberfläche draufsetzen. Diese läuft als Schleife, in der Kommandoeingaben abgefragt und ausgewertet werden. Die verfügbaren Kommandos können mit der help - Funktion sichtbar gemacht werden. Wie sieht solche kleine shell nun aus? Hier mal ein Beispiel im Kommandofenster von Windows.


Hier wurde als Eingabeaufforderung das Zeichen % verwendet. Nun wird einer der erwarteten Zeichen + Enter (die Zeichen sind hier durch Einklammerung hervorgehoben) die dazu passende Funktion auslösen. Mit "q" + Enter wird das Programm verlassen. Jetzt läuft natürlich auch das Hinzuladen sowie Abspeichern der Daten für den Nutzer unbemerkt (außer dass dem Nutzer auffällt,dass das Programm nun nichts mehr vergisst).

Der Programmquelltext ist in in diesem Listing nachzulesen. Welche Funktionen sind nun verfügbar? Neben der Funktion h = help, die die Zeile mit den möglichen Befehlen erneut auswirft, gibt es die Kommandos:

l  = list:    Ausgabe aller Notizzettelzeilen untereinander
a = add    Hinzufügen einer neuen Notiz an des Ende der Liste
d = del    Entfernen einer noch festzulegenden Notiz aus der Liste
q = quit    Beenden des Programms nach Abspeichern der Daten

Welches Programmstück wird nun wann und wofür abgearbeitet? Die folgenden Erläuterungen sind jetzt etwas gröber als zu Anfang, da wir ja inzwischen schon immer mehr über Pythonprogramme wissen. Die bereits bekannten Funktionen wurden etwas ausgebaut. Das bitte ich, selbst zu vergleichen und auszuprobieren. Aber was ist jetzt echt neu? Da fällt ein ganz neuartiger Programmteil auf:

# ab hier Hauptprogramm

if __name__ == '__main__':
    try:
        n = notiz()
        n.version()
        print
        n.list()
        print
        n.help()
        n.shell()
    except EOFError: pass
    print 'bye'

Wenn in einem Modul neben den Klassen- und Funktionsdefinitionen  noch Befehlszeilen stehen,werden die vom Interpreter sofort ausgeführt. Die Variable __name__hat nur dann den Namen __main__,wenn das Modulals Hauptmodul gestartet wird. Dient es nur als Funktionssammlung eines anderen aufrufenden Moduls, dann hat es den Namen nach seinem Dateinamen (also z.B. todo) Die Befehle in diesem Programmblock sollen nur arbeiten, wenn todo das Hauptprogramm ist. Wollen wir die Funktionen für etwas anderes nutzen (dieser Fall kommt noch, wenn wir die grafische Oberfläche drauflegen), würden diese Befehle nur stören.

Nun wird hier automatisch allerlei gemacht. So erzeugt das Programm für uns die Instanz n von notiz.Weiterhin wird eine wichtige Funktionaufgerufen,die n.shell( ) heißt. Hier wird auf unsere Eingaben gewartet und die zu den Kommandos passenden Funktionenaufgerufen. Die shell läuft in einer while - Schleife mit der Bedingung 1 (dauerwahr), bis wir das durch den Kommando quit mit dem Befehl break (Herausspringen aus der Schleife) beenden.

 def shell(self):
        while 1:
            # das nächste Kommando eingeben
           befehl = raw_input('% ')
            # ab hier das eingegebene Kommando auswerten
           if befehl == 'h':
                self.help()
            elif befehl == 'l':
                self.list()
            elif befehl == 'a':
            # die neue Notiz eingeben
               eintrag = raw_input('new: ')
                self.hinzu(eintrag)
            elif befehl == 'd':
                posi = input('number of position: ')
                # wo sitzt der zu löschende Eintrag?
                n.raus(posi)
            elif befehl == 'q':
                self.rette()
                break
            else:
                # es kam ein Kommando, das nicht vorgesehen ist
                print "command not defined!"
 

Folgende Funktionen zur Verwaltung der Liste könnten noch sinnvoll dazugenommen werden, wie
(e)dit,      - zum Editieren einer Aktivitätszeile
(u)p,        - um die Position einer Aktivität wegen ihrer Prioritätin in der Liste um 1 Zeile anzuheben,
(i)nsert    - die nächste neue Eintragung wird nicht hinten angefügt (add), sondern an einer gewählten Position eingefügt

Dies sind nur Beispiele, der eigenen Kreativität sind keine Grenzen gesetzt. :-)
In einem späteren Beispiel desselben Programms, allerdings mit einer grafischen Bedienoberfläche, wird z.B. die Funktion up mit realisiert.


Verwalten der ToDo's in mehreren Listen

Als nächsten Schritt wollen wir unser ToDo-Programm etwas ausbauen, da es uns nicht ausreicht, alle wichtigen Aktivitäten in einer einzigen Liste zu verwalten. Viel besser wäre es, wenn wir mit mehreren Listen arbeiten könnten, zwischen denen man "umschalten" kann. Gearbeitet wird mit der jeweils "aktuellen Liste".
Eine solche Variante ist im zugehörigen Listing gezeigt. Wenn auch noch nicht alle Funktionen implemetiert sind, kann man trotzdem die Funktionsweise verstehen. Auch hier ein Ansichtsbeispiel, zur Abwechselung mal unter Linux. In der Hilfefunktion ist zu sehen, dass nun neue Funktionen hinzugekommen sind, die z.B. das Wechseln der Ansicht zwischen möglichen verschiedenen Listen ermöglichen.

Beim Transfer eines Python - Quelltextes von Windows nach Linux gibt es übrigens noch das Problem, dass die Wagenrückläufe des DOS/Windows - Formates nun unter Linux stören. Das geht so weit, dass das Programm nicht mehr läuft. Wenn man im Editor von Idle nachschaut, ist der Schlamassel zu sehen. Alle Zeilen enden mit \r. Um das zu beseitigen, muss nun aber nicht etwa von Hand nachgearbeitet werden. Die verschiedenen Übersetzungsprogrammen, die es unter Linux gibt und die eventuell sogar die Umlaute mit umstellen, machen es recht einfach. So ist z.B. "recode" solch ein Tool.