Was ist, wenn wir die die Grammatik ändern wollen? Wir müssen jedes Mal den Quellcode des Programms ändern. Außerdem ist das Format nicht sehr eingabefreundlich. Wir möchten lieber etwas wie
S -> b S -> aSa
in eine Datei (ein file) schreiben, die das Programm dann liest.
Dateien sind in Python Objekte, die von der eingebauten Funktion open erzeugt werden:
grammarFile = open("grammar")
In modernen Python-Versionen kann man Dateien auch über die “eingebaute Funktion” file erzeugen. In gewisser Weise ist dies besser, weil es analog zu int, str und Freunden funktioniert: Es konstruiert einen Wert mit dem durch seinen Namen gegebenen Typ aus etwas anderem (im Fall von file ist dieses andere praktisch immer ein String mit dem Dateinamen).
Andererseits können alte Python-Versionen nichts damit anfangen (auch wenn man das mit file = open beheben könnte), und die meisten Leute verwenden immer noch open, so dass man wohl vorerst noch bei open bleiben sollte.
Dateien sind eigentlich auch nur eine Datenstruktur – sie unterscheidet sich allerdings von allem, was wir bisher kennen, in einem Punkt: Man kann nicht ohne weiteres auf beliebige Elemente innerhalb der Datei zugreifen. Tatsächlich steht in dem, was wir von open zurückbekommen (das file-Objekt) nicht der Inhalt der Datei, sondern nur etwas, das einem den Zugriff auf die wirklichen Daten erlaubt, die weiter (etwa) auf der Platte liegen.
Der Grund dafür ist, dass Massenspeicher und ähnliche Geräte, die man mit Dateien abstrahiert, anders gebaut sind als der Hauptspeicher, in dem Listen, Dictionaries usf liegen. Die heute üblichen Festplatten beispielsweise sind als rotierende Scheiben realisert, über denen je ein Schreib-/Lesekopf schwebt. Um ein Byte zu lesen, muss man den Kopf zunächst in die richtige Spur bewegen und dann warten, bis es durch die Rotation vorbeiflitzt. Weil das ziemlich aufwändig ist, ist der Zugriff auf Daten am Massenspeicher tief unten an der Hardware so geregelt, dass man immer nur einen ganzen Haufen Zeichen (ein paar hundert oder tausend davon) auf einmal lesen oder schreiben kann. Besonders gut ist, wenn man die Daten in der Reihenfolge haben will, in der sie auf der Platte stehen. Python und das Betriebssystem verstecken einen Teil dieser Schwierigkeiten, trotzdem muss man mit Dateien anders umgehen als, sagen wir, mit Listen.
Der Fall, dass der Zugriff auf Daten mit relativ großem Aufwand verbunden ist und es noch dazu eine Art natürliche Reihenfolge des Lesens oder Schreibens gibt, ist gar nicht so selten – liest man etwa Daten vom Netz oder schreibt sie in einen Digital-Analog-Wandler, der Töne daraus macht, sehen die Verhältnisse sehr ähnlich aus, und in der Tat werden auch in diesen Fällen die wirklichen Vorgänge hinter Dateien oder Objekten, die ganz ähnlich funktionieren, versteckt.
Ein paar Methoden von Dateien:
Damit könnten wir unsere Grammatik so einlesen:
def readRules(inFileName): rules = {} rulesFile = open(inFileName) rawRules = rulesFile.readlines() rulesFile.close() for rawRule in rawRules: leftRight = rawRule.split("->") if len(leftRight)==2: key = leftRight[0].strip() val = leftRight[1].strip() rules.setdefault(key, []).append(val) return rules
Anmerkungen:
In Dateien kann man auch schreiben. Man muss sie dann mit open(<dateiname>, "w") (für “write”) erzeugen, danach kann man per write reinschreiben:
>>> f = open("test", "w") >>> f.write("Hallo Welt\n") >>> f.close() >>> ^D tucana:examples> cat test Hallo Welt
Aus Dateien bekommt ihr durchweg Bytestrings zurück, für die Interpretation der Bytes seid ihr selbst verantwortlich. Analog könnt ihr in Dateien nur Bytestrings schreiben, der Versuch, Unicode-Strings zu schreiben, scheitert, sobald Nicht-ASCII-Zeichen im String sind.
Das ist natürlich lästig, wenn man Sprache verarbeiten möchte, aber nicht zu vermeiden, weil kein mir bekanntes Betriebssystem zu einer Textdatei das von dem schreibenden Programm verwendete Encoding speichert (so ein Datensatz wäre ein Beispiel für so genannte Metadaten, also Daten über Daten – Metadaten, die übliche Betriebssysteme speichern, sind beispielsweise Zugriffszeiten, Eigentümer von Dateien usf.). Verschiedene Kommunikationsprotokolle (z.B. http oder auch das für E-Mail entwickelte MIME) erlauben übrigens, auf Seitenkanälen Information über das Encoding auszutauschen, aber das hilft genau dann, wenn man mit entsprechenden Daten umgeht. Dokumentation dazu findet ihr in den entsprechenden Modulen.
Das bedeutet: Wenn euer Programm Dateien verwendet und ihr Unicode-Strings verwenden wollt, müsst ihr ein Encoding vereinbaren und ensprechend die Strings, die von read() zurückkommen, Dekodieren und zum Schreiben wieder Enkodieren. Im codecs-Modul ist übrigens eine Funktion open enthalten, die File-Objekte zurückgeben, die diese Kodieren und Dekodieren selbst machen. Wir werden im Zusammenhang mit Vererbung sehen, wie man sowas selbst schreiben kann.
Solange ihr aber mit Bytestrings auskommt (das könnt ihr meist, solange ihr euch auf einem Rechner bewegt und lediglich westeuropäische Sprachen verarbeitet) und euer Programm das gleiche Encoding verwendet wie eure Daten, müsst ihr euch um diese Dinge noch keine Gedanken machen.
Ihr solltet euch wenigstens an den rötlich unterlegten Aufgaben versuchen
(1)
Schreibt eine Funktion catUpper(fName) -> None, die den Inhalt der durch fName bezeichneten Datei in Großbuchstaben ausgibt. Wenn ihr keinen anderen Dateinamen zum Probieren wisst, tut es auch der Name des Skripts, in das ihr die Funktion schreibt. Wenn das so ist, solltet ihr euch allerdings schleunigst mit dem Dateisystem eures Betriebssystems auseinandersetzen.
(2)
readlineund Freunde funktionieren nur für Textdateien, d.h. Dateien, in denen auch wirklich Text steht. Für Binärdateien (also solche, die nicht aus Zeilen mit harmlosen Text bestehen) kommen unter Umständen sehr komische Dinge heraus. Probiert mal, was folgendes tut:
>>> f = open("...(Pfad zu Binärdatei)...") >>> f.readline() >>> f.readline() ...
Unter Unix empfehle ich als Binärdatei etwas wie /bin/cat oder /lib/libc.so, unter Windows ist die Verwendung einer Word-Datei recht instruktiv.
(3)
Schreibt eine Funktion filecopy(fName1, fName2) -> None, die eine Textdatei fName1 nach fName2 kopiert und dabei nicht allzu viel Hauptspeicher braucht.