2.5 Umgang mit Textdateien und Strings Beim Abschlussprojekt Autorschaftstest müssen Texte, die in Dateien gespeichert sind, verarbeitet werden. Dazu gibt es jetzt ein paar Beispiele und Anmerkungen. Strings Pythons Datentyp string dient zum Speichern und Bearbeiten von Zeichenfolgen. Ein Strings ist zwar kein Array, aber die üblichen Index-Operationen gehen auch mit Strings. import stdio # Strings kann man mit '...' oder "..." begrenzen. a = 'abcde' b = "12'45" s = '' for i in range(len(a)): s = s + a[i] + b[-(i+1)] stdio.writeln(s) #------------------------------------------------------# python string1.py # a5b4c'd2e1 2.5.1 Umlaute Wenn man Kommentar mit Umlauten einfügt, gibt es eine Fehlermeldung. import stdio # Strings kann man mit '...' oder "..." begrenzen. a = 'abcde' b = "12'45" s = '' # Verschränke a vorwärts und b rückwärts ineinander. for i in range(len(a)): s = s + a[i] + b[-(i+1)] stdio.writeln(s) #------------------------------------------------------# python string1.py # File "string1.py", line 9 # SyntaxError: Non-ASCII character '\xc3' in file string1.py on line 9, but no # encoding declared; see http://www.python.org/peps/pep-0263.html for details 2.5.2 Kodierung gemäß ASCII ASCII ... American Standard Code for Information Interchange Jedes Zeichen wird in einem Speicherwort aus 8 Bit gespeichert, z.B. a durch 0110 0001 (dezimal 97, hexadezimal 61). Davon werden 7 Bit benutzt – das reicht für 128 verschiedene Zeichen. Umlaute sind nicht im ASCII-Code. 2.5.3 Kodierung gemäß UTF-8 UTF-8 ... 8-Bit Universal Character Set Transformation Format ASCII Zeichen werden in UTF-8 wie in ASCII dargestellt (8 Bit). Weitere Zeichen werden durch 2 Speicherwörter aus 8 Bit dargestellt, z.B. ä durch 1100 0011 1010 0100 (hexadezimal c3a4). Bei ASCII-Kodierung ist das erste Bit stets 0. Wenn ein Zeichen mit UTF-8 durch mehrere Speicherwörter kodiert wird, dann gibt die Anzahl der 1en am Anfang des ersten Wortes an, wieviele Speicherwörter benutzt werden. 2.5.4 Umlaute Also muss man ein encoding in den Programmtext einfügen, in dem Umlaute enthalten sind (und das vom Editor beim Schreiben des Programms benutzt wurde). # encoding: utf-8 import stdio # Strings kann man mit '...' oder "..." begrenzen. a = 'abcde' b = "12'45" s = '' # Verschränke a vorwärts und b rückwärts ineinander. for i in range(len(a)): s = s + a[i] + b[-(i+1)] stdio.writeln(s) #------------------------------------------------------# python string1.py # a5b4c'd2e1 Umlaute in Strings Wenn man Umlaute im String hat, gibt es eine andere Fehlermeldung. # encoding: utf-8 import stdio # Strings kann man mit '...' oder "..." begrenzen. a = 'äbcde' b = "12'45" s = '' # Verschränke a vorwärts und b rückwärts ineinander. for i in range(len(a)): s = s + a[i] + b[-(i+1)] stdio.writeln(s) #------------------------------------------------------# python string1.py # Traceback (most recent call last): # File "string1.py", line 12, in <module> # s = s + a[i] + b[-(i+1)] # IndexError: string index out of range Also ist a jetzt länger als b! (Und das ist nicht das einzige Problem . . . ) 2.5.6 String ‘ä’ ist länger als String "a" # encoding: utf-8 import stdio stdio.writeln(len('ä')) stdio.writeln(len('a')) #------------------------------------------------------# python string2.py # 2 # 1 2.5.7 Kann man Strings mit Umlauten indiziert durchlaufen? # encoding: utf-8 import stdio s = 'Lärm' for i in range(len(s)): stdio.writeln(s[i]) #------------------------------------------------------# python string3.py # L # Traceback (most recent call last): # File "string3.py", line 8, in <module> # stdio.writeln(a[i]) # File "/usr/local/lib/python2.7/dist-packages/stdio.py", line 42, in writeln # x = unicode(x) # UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 0: ordinal not i Mit print(a[i]) anstelle von stdio.writeln(s[i]) gibt es keine Fehlermeldung, aber auch keine richtige Ausgabe. 2.5.8 Da hilft nur noch der Umstieg auf Python 3. import stdio s = 'Lärm' for i in range(len(s)): stdio.writeln(s[i]) #------------------------------------------------------# python3 string3.py # L # ä # r # m Dort wird alles in UTF-8 gemacht. 2.5.9 Schreiben wie Goethe Zufällige Texte erzeugen Das Beispiel auf den folgenden Seiten führt zum Erzeugen eines zufälligen Satzes. Dazu wird § eine Textdatei eingelesen (z.B. Goethes Faust oder Joyces Ulysses) § alle Paare von aufeinanderfolgenden Wörtern gefunden § ein Satz erzeugt, in dem jedes Paar aufeinanderfolgender Wörter auch in der gelesenen Textdatei vorkommt. Direktes Einlesen von Dateien Man kann Dateien zeilenweise als Strings einlesen. # einlesen.py import sys, stdio, instream def liesText(dateiname): # Lies Datei dateiname ein und speichere sie in einem String (mittels readAll()) # instream.InStream() liest z.B. aus einer Datei und kann # benutzt werden wie standard input. text = instream.InStream(dateiname).readAll() return text #----------------------------------------------------------------------------------def test(): dateiname = sys.argv[1] text = liesText(dateiname) stdio.writeln(text) #----------------------------------------------------------------------------------if __name__ == '__main__': test() 2.5.11 python3 einlesen.py SchillerBriefe3.txt 0 Sie haben durch den schönen Beitrag, den Sie in Ihrem _Dante_ zu den 1 Horen gegeben, ein zu entschiedenes Verdienst um den glücklichen 2 Fortgang dieses Journals, als dass ich Ihnen nicht den verbindlichsten 3 Dank dafür sagen sollte. Ich thue dieß um so lieber, da es mich zugleich ... python3 einlesen.py http://www.gutenberg.org/cache/epub/7939/pg7939.txt 0 Project Gutenberg's Die Huldigung der Kuenste, by Friedrich Schiller 1 #41 in our series by Friedrich Schiller 2 3 Copyright laws are changing all over the world. Be sure to check the 4 copyright laws for your country before downloading or redistributing 5 this or any other Project Gutenberg eBook. 6 ... 2.5.12 Die Funktion einstring(d) macht einen String aus der ganzen Datei. # einlesen.py import string def einstring(dateiname): # Lies Datei dateiname als einen (langen) String datei = open(dateiname,"r") text_als_ein_string = '' for zeile in datei: text_als_ein_string += string.rstrip(zeile) datei.close() return text_als_ein_string def test(): taes = einstring("SchillerBriefe3.txt") print taes if __name__ == '__main__': test() Die Funktion string.rstrip(z) entfernt Leerzeichen und Zeilenumbrüche am Ende des Strings z. 2.5.13 Zerlegen eines Textes in Wörter Die folgende Funktion zerlege.woerterliste(s) § erhält als Eingabe einen String s, der z.B. durch Einlesen eines Textes aus einer Datei entstanden ist, § ersetzt alle Satzendezeichen durch . “ ” (dadurch wird der Punkt durch Leerzeichen vom Rest getrennt), § entfernt Zeichen aus dem String, die wir nicht brauchen, und § trennt den String an allen (Folgen von) Leerzeichen auf und erhält dadurch ein Array aus den Teilen des Strings (d.h. den Wörtern), die zwischen zwei Leerzeichen stehen. In dem Array stehen die Wörter in der gleichen Reihenfolge wie im Text. Als Ergebnis wird das Array der Wörter zurückgegeben. Beispiel für die Anwendung von zerlege: more SchillerBriefe3.txt Sie haben durch den schönen Beitrag, den Sie in Ihrem _Dante_ zu den Horen gegeben, ein zu entschiedenes Verdienst um den glücklichen Fortgang dieses Journals, als dass ich Ihnen nicht den verbindlichsten Dank dafür sagen sollte. Ich thue dieß um so lieber, da es mich zugleich python3 zerlege.py SchillerBriefe3.txt ['Sie', 'haben', 'durch', 'den', 'schönen', 'Beitrag', 'den', 'Sie', 'in', 'Ihrem', 'Dante', 'zu', 'den', 'Horen', 'gegeben', 'ein', 'zu', 'entschiedenes', 'Verdienst', 'um', 'den', 'glücklichen', 'Fortgang', 'dieses', 'Journals', ... ] 2.5.15 API für str.replace(wort,alt,neu) wort, alt und neu sind vom Typ string. Es wird der String zurückgegeben, der entsteht, wenn man in wort jedes Vorkommen von alt durch neu ersetzt. Beispiel: str.replace("Hallo. Wie gehts? Gut.", ".", "!") liefert 'Hallo! Wie gehts? Gut!' str.replace("Teilnehmer: Anna, Bert, Chris", "Bert", "Peter") liefert 'Teilnehmer: Anna, Peter, Chris' In Python2 ist replace() nicht im Modul str, sondern im Modul string. API für str.split(wort,trenner) wort und trenner sind vom Typ string. Es wird ein Array zurückgegeben, dessen Einträge die Teile von wort, vor denen und hinter denen trenner steht. Beispiel: str.split(' abc de f gg \n',' ') liefert ['', '', 'abc', '', 'de', 'f', 'gg', '\n'] 2.5.17 #-------------------------------------------------------# zerlege.py #-------------------------------------------------------import einlesen def woerterliste(text): # Ersetze alle Satzendezeichen durch " ." for zeichen in "?!.": text = str.replace(text,zeichen," .") # Ersetze störende Zeichen durch " ". for zeichen in '''"',;:-)([]<>*#_\n\t\r''': text = str.replace(text,zeichen," ") # Zerlege den Text in Wörter. woerter = str.split(text,' ') # Entferne "leere Wörter". ergebnis = [] for i in range(len(woerter)): if len(woerter[i])>=1: ergebnis += [woerter[i]] return ergebnis #----------------------------------------------------------def test(): text = einlesen.liesAlles("SchillerBriefe.txt") stdio.writeln(woerterliste(text)) #----------------------------------------------------------if __name__ == '__main__': test() 2.5.18 Dictionaries Ein Dictionary ist eine Liste aus Paaren ’key’:’value’. Der value kann über den key angesprochen werden. # woerterbuch.py # Erzeuge ein leeres Dictionary. woerterbuch = {} # Trage Wortpaare ein. woerterbuch['Aal'] = 'eal' woerterbuch['Adler'] = 'eagle' woerterbuch['Buch'] = 'book' print woerterbuch for i in woerterbuch: print i, woerterbuch[i] #----------------------------------------------------------------------------------# python woerterbuch.py # {'Aal': 'eal', 'Buch': 'book', 'Adler': 'eagle'} # Aal eal # Buch book # Adler eagle 2.5.19 Erzeugen eines Dictionaries mit aufeinanderfolgenden Wörtern eines Textes Die Aufbereitung des Textes wird abgeschlossen mit der Erstellung eines Dictionaries, in dem zu jedem Wort x des Textes ein Array aller Wörter steht, die im Text auf x folgen. Der Dictionary-Eintrag ’heute’ : [’ist’, ’wird’, ’ist’, ’ein’, ’mal’, ’.’] steht dafür, dass im Text auf das Wort heute zweimal das Wort ist und jeweils einmal das Wort wird, ein bzw. mal und einmal das Satzendezeichen . folgt. #-------------------------------------------------------# wortpaare.py #-------------------------------------------------------import sys, zerlege, einlesen def konkordanz(woerterliste): wortfolgen = {} for i in range(len(woerterliste)-1): if not woerterliste[i] in wortfolgen: wortfolgen[woerterliste[i]] = [] wortfolgen[woerterliste[i]] += [woerterliste[i+1]] return wortfolgen def test(): text = einlesen.liesAlles(sys.argv[1]) woerterliste = zerlege.woerterliste(text) liste = konkordanz(woerterliste) print liste if __name__ == '__main__': test() Aus einem Text wird ein Dictionary erzeugt, das zu jedem Wort im Text einen String mit allen seinen direkten Folgewörtern enthält. 2.5.21 Erzeugen eines Satzes wie im Text“ ” Schließlich können wir einen Satz erzeugen. Wir beginnen mit einem Wort, das hinter einem .“ steht. ” Anschließend wählen wir aus dem Dictionary wiederholt ein Folgewort aus, bis wir wieder bei einem .“ ankommen. ” 2.5.22 # schreibe.py import sys, stdio, zerlege, einlesen, wortpaare, random def schreibe_einen_satz(k): satzende = False satz = '' wort = "." while not satzende: wuerfelwurf = random.randrange(len(k[wort])) wort = k[wort][wuerfelwurf] if wort==".": satzende = True else: satz = satz + ' ' + wort return satz def test(): text = einlesen.liesAlles(sys.argv[1]) woerterliste = zerlege.woerterliste(text) liste = wortpaare.konkordanz(woerterliste) stdio.writeln(schreibe_einen_satz(liste) + ".") if __name__ == '__main__': test() 2.5.23 Zusammenfassung Leider kommt hauptsächlich Unsinn heraus ... python3 schreibe.py SchillerBriefe.txt Erhalten Sie uns ruhig unterhalten. python3 schreibe.py http://www.gutenberg.org/ebooks/24677.txt.utf-8 Noch nie erlebte ich den millionenmal stärkeren Mittagssonnenschein. Aber wir haben etwas über den Umgang mit Strings gelernt.