7. Die Textdateien

7.1. Skript [fic_01]: Lesen/Schreiben einer Textdatei
Das folgende Skript veranschaulicht ein Beispiel für die Verarbeitung von Textdateien:
# Importe
import sys
# Erstellung und anschließende sequenzielle Verarbeitung einer Textdatei
# Diese besteht aus einer Reihe von Zeilen der Form login:pwd:uid:gid:infos:dir:shell
# Jede Zeile wird in einem Wörterbuch in der Form „login => uid:gid:infos:dir:shell“ gespeichert
# --------------------------------------------------------------------------
def affiche_infos(dico: dict, clé: str):
# Zeigt den Wert an, der dem Schlüssel im Wörterbuch „dico“ zugeordnet ist, sofern dieser existiert
if clé in dico.keys():
# Der dem Schlüssel zugeordnete Wert wird angezeigt
print(f"{clé} : {dico[clé]}")
else:
# Der Schlüssel ist kein Schlüssel des Wörterbuchs „dico“
print(f"la clé [{clé}] n'existe pas")
# main -----------------------------------------------
# Der Dateiname wird festgelegt
FILE_NAME = "./data/infos.txt"
# Erstellung und Befüllung der Textdatei
fic = None
try:
# Öffnen der Datei zum Schreiben (w=write)
fic = open(FILE_NAME, "w")
# Es wird ein beliebiger Inhalt generiert
for i in range(1, 101):
# eine Zeile
ligne = f"login{i}:pwd{i}:uid{i}:gid{i}:infos{i}:dir{i}:shell{i}"
# wird in die Textdatei geschrieben
fic.write(f"{ligne}\n")
except IOError as erreur:
print(f"Erreur d'exploitation du fichier {FILE_NAME} : {erreur}")
sys.exit()
finally:
# Die Datei wird geschlossen, falls sie geöffnet wurde
if fic:
fic.close()
# die Datei wird zum Lesen geöffnet
fic = None
try:
# Öffnen der Datei zum Lesen
fic = open(FILE_NAME, "r")
# Das Wörterbuch ist zu Beginn leer
dico = {}
# Jede Zeile wird in das Wörterbuch [dico] in der Form „login => uid:gid:infos:dir:shell“ aufgenommen
# Lesen der ersten Zeile unter Entfernung der Leerzeichen am Zeilenanfang und -ende
ligne = fic.readline().strip()
# solange die Zeile nicht leer ist
while ligne != '':
# wird die Zeile in ein Array aufgenommen
infos = ligne.split(":")
# das Login wird abgerufen
login = infos[0]
# das Passwort wird ignoriert
infos[0:2] = []
# ein Eintrag im Wörterbuch wird angelegt
dico[login] = infos
# nächste Zeile lesen
ligne = fic.readline().strip()
except IOError as erreur:
print(f"Erreur d'exploitation du fichier {FILE_NAME} : {erreur}")
sys.exit()
finally:
# Die Datei wird geschlossen, falls sie geöffnet war
if fic:
fic.close()
# Auswertung des Wörterbuchs „dico“
affiche_infos(dico, "login10")
affiche_infos(dico, "X")
Hinweise:
- Zeile 28: Öffnen der Datei zum Schreiben (w=write). Wenn die Datei bereits existiert, wird sie überschrieben;
- Zeilen 30–34: Es werden 100 Zeilen in der Textdatei erzeugt;
- Zeile 34: Zum Schreiben einer Zeile in die Textdatei. Die Methode [write] fügt kein Zeilenendezeichen hinzu. Dieses muss daher im geschriebenen Text vorgesehen werden;
- Zeilen 35–37: Behandlung einer möglichen Ausnahme;
- Zeile 37: Abbruch der Skriptausführung (jedoch erst nach Ausführung der finally-Klausel);
- Zeilen 38–41: In jedem Fall, ob Fehler vorliegt oder nicht, wird die Datei geschlossen, sofern sie geöffnet ist;
- Zeile 47: Öffnen der Datei zum Lesen (r=read);
- Zeile 49: Definition eines leeren Wörterbuchs;
- Zeile 52: Die Methode [readline] liest eine Textzeile, einschließlich des Zeilenendezeichens. Die Methode [strip] entfernt die „Leerzeichen“ am Anfang und am Ende der Zeichenkette. Unter „Leerzeichen“ sind Leerzeichen, Zeilenendezeichen, Seitenumbrüche, Tabulatoren und einige andere Zeichen zu verstehen. Daher enthält [ligne] hier keine Zeilenendezeichen wie [\r\n] (Windows) oder [\n] (Unix);
- Zeile 54: Die Datei wird so lange verarbeitet, bis eine leere Zeile gefunden wird;
- Zeilen 54–64: Die Textdatei wird in das Wörterbuch [dico] übertragen. Der Schlüssel ist das Feld [login], der Wert sind die Felder [uid:gid:infos:dir:shell];
- Zeilen 65–67: Behandlung einer möglichen Ausnahme;
- Zeilen 68–71: Schließen der Datei in jedem Fall, unabhängig davon, ob ein Fehler vorliegt oder nicht;
- Zeilen 74–75: Auswertung des Wörterbuchs [dico];
Die Datei [data/infos.txt]:
Die Bildschirmausgaben:
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/fichiers/fic_01.py
login10 : ['uid10', 'gid10', 'infos10', 'dir10', 'shell10']
la clé [X] n'existe pas
Process finished with exit code 0
7.2. Skript [fic_02]: Verwaltung von Textdateien, die in UTF-8 kodiert sind
Im weiteren Verlauf dieses Dokuments werden wir ausschließlich in UTF-8 kodierte Textdateien bearbeiten. Zunächst konfigurieren wir PyCharm:

- in [5-6]: Wählen Sie die Kodierung UTF-8 für die Projektdateien;
Um eine in UTF-8 kodierte Datei zu erstellen, können Sie wie folgt vorgehen (fic-02):
# Importe
import codecs
# Schreiben von UTF-8 in eine Textdatei
# Ausnahmen werden nicht behandelt
file=codecs.open("./data/utf8.txt","w","utf8")
file.write("Hélène est partie à Bâle pendant l'été chez sa grand-mère")
file.close()
Hinweise
- Zeile 2: Um die Kodierung der Dateien zu verwalten, wird das Modul [codecs] importiert;
- Zeile 6: Die Methode [codecs.open] wird wie die klassische Funktion [open] verwendet. Man kann jedoch die gewünschte (beim Erstellen) oder die vorhandene (beim Lesen) Kodierung angeben. Nach dem Öffnen wird das in Zeile 6 erhaltene Objekt [file] wie eine herkömmliche Datei verwendet;
- Zeile 7: Es wurden Zeichen mit Akzenten verwendet, die je nach verwendetem Zeichensatz meist unterschiedlich dargestellt werden;
Ergebnisse
Wenn man die erhaltene Datei [data/utf8.txt] (siehe Zeile 6) öffnet, erhält man folgendes Ergebnis:

7.3. Skript [fic_03]: Verwaltung von Textdateien, die mit ISO-8859-1 kodiert sind
Das Skript [fic_03] führt dieselben Schritte aus wie das Skript [fic_02], kodiert die Textdatei jedoch in ISO-8859-1. Wir möchten den Unterschied zwischen den resultierenden Dateien aufzeigen:
# Importe
import codecs
# Schreiben von ISO-8859-1 in eine Textdatei
# Ausnahmen werden nicht behandelt
file=codecs.open("./data/iso-8859-1.txt","w","iso-8859-1")
file.write("Hélène est partie à Bâle pendant l'été chez sa grand-mère")
file.close()
Wenn man die in Zeile 6 erstellte Datei „[data/iso-8859-1]“ öffnet, erhält man folgendes Ergebnis:

Da wir das Projekt so konfiguriert haben, dass es mit UTF-8-Dateien arbeitet, hat PyCharm versucht, die Datei [iso-8859-1.txt] als UTF-8 zu öffnen. Es erkennt anhand von „[1]“, dass es sich bei der Datei nicht um „UTF-8“ handelt. Daher schlägt es vor, die Datei in einer anderen Kodierung neu zu laden:

- in [3-5]: Die Datei wird unter Verwendung der Kodierung ISO-8859-1 neu geladen;

- in [6], dieselbe Datei, jedoch mit einer anderen Kodierung angezeigt;
Wenn man zu den Projekteinstellungen zurückkehrt:

- sehen wir, dass PyCharm bei [6-7] vermerkt hat, dass die Datei [iso-8859-1.txt] mit der Kodierung ISO-8859-1 geöffnet werden sollte. Dies ist also eine Ausnahme von der Regel [5];
7.4. Skript [json_01]: Verwaltung einer Datei jSON
JSON steht für JavaScript Object Notation. Wie der Name schon sagt, handelt es sich um eine textuelle Darstellungsweise von Objekten der Programmiersprache JavaScript. Wir werden sie hier mit Python-Objekten verwenden.
Die verwaltete Datei jSON ([data/in.json]) sieht wie folgt aus:

- In [2] sieht man, dass der Textinhalt der Datei [in.json] ein Python-Wörterbuch darstellen könnte. PyCharm hat diesen Text formatiert (Strg-Alt-L), aber selbst wenn er in einer einzigen Zeile stünde, würde das nichts ändern. Die Form des Textes spielt keine Rolle, solange er syntaktisch ein Python-Objekt darstellt;
Das Skript [json-01] zeigt, wie diese Datei genutzt werden kann:
# Importe
import codecs
import json
import sys
# Lesen/Schreiben einer Datei jSON
inFile=None
outFile=None
try:
# Öffnen der Datei jSON im Lesemodus
inFile = codecs.open("./data/in.json", "r", "utf8")
# Übertragung des Inhalts in ein Wörterbuch
data = json.load(inFile)
# Anzeige der eingelesenen Daten
print(f"data={data}, type(data)={type(data)}")
limites = data['limites']
print(f"limites={limites}, type(limites)={type(limites)}")
print(f"limites[1]={limites[1]}, type(limites[1])={type(limites[1])}")
# Übertragung des Wörterbuchs [data] in eine JSON-Datei
outFile = codecs.open("./data/out.json", "w", "utf8")
json.dump(data, outFile)
except BaseException as erreur:
# Der Fehler wird angezeigt und das Programm wird beendet
print(f"L'erreur suivante s'est produite : {erreur}")
sys.exit()
finally:
# Schließen der Dateien, falls diese geöffnet sind
if inFile:
inFile.close()
if outFile:
outFile.close()
Hinweise
- Zeile 3: Um JSON zu verarbeiten, wird das Modul [json] importiert;
- Zeile 11: Wir werden jSON-Dateien verarbeiten, die in UTF-8 kodiert sind. Hier öffnen wir die Datei [data/in.json] mit dem Modul [codecs];
- Zeile 13: Die Methode [json.load] liest den Inhalt der Datei jSON und speichert ihn in der Variablen [data]. Der Typ dieser Variablen ist hier ein Wörterbuch;
- Zeilen 15–18: Um zu zeigen, dass tatsächlich ein Python-Wörterbuch erhalten wurde, werden einige Elemente daraus ausgegeben;
- Zeilen 20–21: Wir führen den umgekehrten Vorgang durch: Das Wörterbuch [data] wird mithilfe der Methode [json.dump] in eine Datei mit dem Namen UTF-8 geschrieben;
- Zeilen 22–25: Behandlung einer möglichen Ausnahme;
- Zeilen 26–31: Unabhängig davon, ob ein Fehler aufgetreten ist oder nicht, werden alle möglicherweise geöffneten Dateien geschlossen;
Ergebnisse
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/fichiers/json_01.py
data={'limites': [9964, 27519, 73779, 156244, 0], 'coeffR': [0, 0.14, 0.3, 0.41, 0.45], 'coeffN': [0, 1394.96, 5798, 13913.69, 20163.45], 'PLAFOND_QF_DEMI_PART': 1551, 'PLAFOND_REVENUS_CELIBATAIRE_POUR_REDUCTION': 21037, 'PLAFOND_REVENUS_COUPLE_POUR_REDUCTION': 42074, 'VALEUR_REDUC_DEMI_PART': 3797, 'PLAFOND_DECOTE_CELIBATAIRE': 1196, 'PLAFOND_DECOTE_COUPLE': 1970, 'PLAFOND_IMPOT_COUPLE_POUR_DECOTE': 2627, 'PLAFOND_IMPOT_CELIBATAIRE_POUR_DECOTE': 1595, 'ABATTEMENT_DIXPOURCENT_MAX': 12502, 'ABATTEMENT_DIXPOURCENT_MIN': 437}, type(data)=<class 'dict'>
limites=[9964, 27519, 73779, 156244, 0], type(limites)=<class 'list'>
limites[1]=27519, type(limites[1])=<class 'int'>
Process finished with exit code 0
- Die Zeilen 2–4 zeigen, dass das in der Datei jSON enthaltene Wörterbuch korrekt abgerufen wurde;
Sehen wir uns nun den Inhalt der Datei [data/out.json] an:

Der Text der Datei steht in einer einzigen Zeile. PyCharm erkennt jedoch die Dateien jSON, und man kann sie wie Python-Dateien und andere mit Strg-Alt-L formatieren. Man erhält dann Folgendes:

7.5. Skript [json_02]: Verwaltung von jSON-Dateien, die in UTF-8 kodiert sind
Eine in UTF-8 kodierte jSON-Datei kann zwei Formen annehmen:
# Importe
import codecs
import json
import sys
# Wörterbuch
data = {'marié': 'oui', 'impôt': 1340}
# Schreiben einer Datei jSON
out_file1 = None
out_file2 = None
try:
# Übertragung des Wörterbuchs [data] in eine JSON-Datei
out_file1 = codecs.open("./data/out1.json", "w", "utf8")
json.dump(data, out_file1, ensure_ascii=True)
# Übertragung des Wörterbuchs [data] in eine JSON-Datei
out_file2 = codecs.open("./data/out2.json", "w", "utf8")
json.dump(data, out_file2, ensure_ascii=False)
except BaseException as erreur:
# Der Fehler wird angezeigt und das Programm wird beendet
print(f"L'erreur suivante s'est produite : {erreur}")
sys.exit()
finally:
# Schließen der Dateien, falls diese geöffnet sind
if out_file1:
out_file1.close()
if out_file2:
out_file2.close()
…
- In diesem Skript wird das Wörterbuch [data] (Zeile 7) in zwei Dateien jSON (Zeilen 14, 17) geschrieben;
- Zeilen 14, 17: In beiden Fällen wird eine Textdatei mit dem Namen UTF-8 erstellt;
- Zeile 15: Beim Schreiben des Wörterbuchs wird der Parameter mit dem Namen [ensure_ascii=True] verwendet;
- Zeile 18: Beim Schreiben des Wörterbuchs wird der Parameter mit dem Namen [ensure_ascii=False] verwendet;
Hier sind die beiden resultierenden Dateien:

- In der Datei [out1.json] wurden die Zeichen mit Akzenten durch eine Zeichenfolge ersetzt, die ihren Code UTF-8 darstellt. Man sagt manchmal, sie seien „escapet“ worden. Technisch gesehen findet man in der Binärdatei [out1.json] für das Zeichen „é“ aus [marié] nacheinander die Binärcodes UTF-8 der 6 Zeichen aus [\u00e9];
- in der Datei [out2.json] wurden die Zeichen mit Akzenten unverändert belassen. Das bedeutet, dass diese Zeichen in der Binärdatei von [out2.json] durch ihren Binärcode UTF-8 dargestellt werden (also nur ein Code UTF-8 statt 6 für out1). Für das Zeichen „é“ aus [marié] findet man somit den Binärcode [00e9] auf 4 Bytes;
- der Wert des Parameters [ensure_ascii] der Methode [json.dump] bestimmt das verwendete Format;
Einige Anwendungen verwenden „escapte“ UTF-8 für ihre jSON-Dateien. In diesem Fall muss der Wert [ensure_ascii=True] verwendet werden. Dieser Wert ist tatsächlich der Standardwert. Wenn also der Parameter [ensure_ascii] nicht verwendet wird, wird mit „escaped“ jSON- und UTF-8-Dateien gearbeitet.
Das Skript wird wie folgt fortgesetzt:
# Importe
import codecs
import json
import sys
# Wörterbuch
data = {'marié': 'oui', 'impôt': 1340}
…
# Dateien erneut einlesen jSON
in_file1 = None
in_file2 = None
try:
# Übertragung der Datei jSON 1 in ein Wörterbuch
in_file1 = codecs.open("./data/out1.json", "r", "utf8")
dico1 = json.load(in_file1)
# Anzeige
print(f"dico1={dico1}")
# Übertragung der Datei jSON 2 in ein Wörterbuch
in_file2 = codecs.open("./data/out2.json", "r", "utf8")
dico2 = json.load(in_file2)
# Anzeige
print(f"dico2={dico2}")
except BaseException as erreur:
# Fehler wird angezeigt und das Programm wird beendet
print(f"L'erreur suivante s'est produite : {erreur}")
sys.exit()
finally:
# Schließen der Dateien, falls diese geöffnet sind
if in_file1:
in_file1.close()
if in_file2:
in_file2.close()
Anmerkungen
- Zeilen 11–34: Einlesen der beiden Dateien „[out1.json, out2.json]“ und Anzeige des jeweils eingelesenen Wörterbuchs;
Ergebnisse
Überraschenderweise stellt man fest, dass es nicht notwendig war, der Funktion [json.load] (Zeilen 17, 22) die Art der Kodierung (mit oder ohne Escape-Zeichen) der einzulesenden Zeichenkette jSON anzugeben. In beiden Fällen wird das richtige Wörterbuch abgerufen.