7. De tekstbestanden

7.1. Script [fic_01]: lezen/schrijven van een tekstbestand
Het volgende script illustreert een voorbeeld van het gebruik van tekstbestanden:
# imports
import sys
# aanmaken en vervolgens sequentieel verwerken van een tekstbestand
# dit bestand bestaat uit regels in de vorm login:pwd:uid:gid:infos:dir:shell
# elke regel wordt in een woordenboek opgeslagen in de vorm login => uid:gid:infos:dir:shell
# --------------------------------------------------------------------------
def affiche_infos(dico: dict, clé: str):
# geeft de waarde weer die aan de sleutel in het woordenboek ‘dico’ is gekoppeld, indien deze bestaat
if clé in dico.keys():
# de waarde die aan de sleutel is gekoppeld, wordt weergegeven
print(f"{clé} : {dico[clé]}")
else:
# de sleutel komt niet voor in het woordenboek 'dico'
print(f"la clé [{clé}] n'existe pas")
# main -----------------------------------------------
# de bestandsnaam wordt vastgelegd
FILE_NAME = "./data/infos.txt"
# het tekstbestand wordt aangemaakt en gevuld
fic = None
try:
# het bestand openen voor schrijven (w=write)
fic = open(FILE_NAME, "w")
# willekeurige inhoud genereren
for i in range(1, 101):
# een regel
ligne = f"login{i}:pwd{i}:uid{i}:gid{i}:infos{i}:dir{i}:shell{i}"
# wordt naar het tekstbestand geschreven
fic.write(f"{ligne}\n")
except IOError as erreur:
print(f"Erreur d'exploitation du fichier {FILE_NAME} : {erreur}")
sys.exit()
finally:
# het bestand wordt gesloten als het geopend was
if fic:
fic.close()
# het bestand wordt geopend in leesmodus
fic = None
try:
# het bestand wordt geopend in leesmodus
fic = open(FILE_NAME, "r")
# het woordenboek is aanvankelijk leeg
dico = {}
# elke regel wordt in het woordenboek [dico] opgenomen in de vorm login => uid:gid:infos:dir:shell
# eerste regel lezen en spaties aan het begin en einde van de regel verwijderen
ligne = fic.readline().strip()
# zolang de regel niet leeg is
while ligne != '':
# de regel in een array plaatsen
infos = ligne.split(":")
# de login wordt opgehaald
login = infos[0]
# het wachtwoord wordt genegeerd
infos[0:2] = []
# er wordt een vermelding in het woordenboek aangemaakt
dico[login] = infos
# de volgende regel lezen
ligne = fic.readline().strip()
except IOError as erreur:
print(f"Erreur d'exploitation du fichier {FILE_NAME} : {erreur}")
sys.exit()
finally:
# het bestand sluiten als het geopend was
if fic:
fic.close()
# het woordenboek 'dico' wordt gebruikt
affiche_infos(dico, "login10")
affiche_infos(dico, "X")
Opmerkingen:
- regel 28: het bestand wordt geopend om te schrijven (w=write). Als het bestand al bestaat, wordt het overschreven;
- regels 30-34: er worden 100 regels in het tekstbestand gegenereerd;
- regel 34: om een regel in het tekstbestand te schrijven. De methode [write] voegt geen regeleinde toe. Dit moet dus in de geschreven tekst worden opgenomen;
- regels 35-37: afhandeling van een eventuele uitzondering;
- regel 37: de uitvoering van het script wordt afgebroken (echter pas na de uitvoering van de finally-clausule);
- regels 38-41: in alle gevallen, of er nu een fout is of niet, wordt het bestand gesloten als het geopend is;
- regel 47: het bestand openen voor lezen (r=read);
- regel 49: definitie van een leeg woordenboek;
- regel 52: de methode [readline] leest een tekstregel, inclusief het einde-van-regel-teken. De methode [strip] verwijdert de „spaties” aan het begin en einde van de tekenreeks. Onder „spatie” worden wittekens verstaan, zoals een regeleinde, een pagina-einde, een tab en enkele andere. Hier zal [ligne] dus niet de regeleinde-tekens van [\r\n] (Windows) of [\n] (Unix) bevatten;
- regel 54: het bestand wordt verwerkt totdat er een lege regel wordt aangetroffen;
- regels 54-64: het tekstbestand wordt overgebracht naar het woordenboek [dico]. De sleutel is het veld [login], de waarde zijn de velden [uid:gid:infos:dir:shell];
- regels 65-67: afhandeling van een eventuele uitzondering;
- regels 68-71: het bestand wordt in alle gevallen gesloten, ongeacht of er een fout is opgetreden;
- regels 74-75: gebruik van het woordenboek [dico];
Het bestand [data/infos.txt]:
De schermresultaten:
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. Script [fic_02]: tekstbestanden beheren die zijn gecodeerd in UTF-8
In het vervolg van dit document gaan we uitsluitend tekstbestanden verwerken die gecodeerd zijn in UTF-8. Eerst gaan we PyCharm configureren:

- in [5-6]: kies de codering UTF-8 voor de bestanden van het project;
Om een bestand te maken dat is gecodeerd in UTF-8, kun je als volgt te werk gaan (fic-02):
# imports
import codecs
# schrijven in UTF-8 naar een tekstbestand
# uitzonderingen worden niet afgehandeld
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()
Opmerkingen
- regel 2: om de codering van de bestanden te beheren, wordt de module [codecs] geïmporteerd;
- regel 6: de methode [codecs.open] wordt op dezelfde manier gebruikt als de klassieke functie [open]. Men kan echter de gewenste (bij het aanmaken) of bestaande (bij het lezen) codering specificeren. Na het openen wordt het in regel 6 verkregen object [file] gebruikt als een klassiek bestand;
- regel 7: er zijn tekens met accenten gebruikt die meestal verschillend worden weergegeven, afhankelijk van de gebruikte tekenset;
Resultaten
Wanneer men het verkregen bestand [data/utf8.txt] (zie regel 6) opent, krijgt men het volgende resultaat:

7.3. Script [fic_03]: tekstbestanden beheren die zijn gecodeerd in ISO-8859-1
Het script [fic_03] doet hetzelfde als het script [fic_02], maar codeert het tekstbestand in ISO-8859-1. We willen het verschil tussen de verkregen bestanden laten zien:
# imports
import codecs
# schrijven in iso-8859-1 naar een tekstbestand
# uitzonderingen worden niet afgehandeld
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()
Wanneer we het bestand [data/iso-8859-1] openen dat op regel 6 is aangemaakt, krijgen we het volgende resultaat:

Omdat we het project hebben geconfigureerd om te werken met UTF-8-bestanden, heeft PyCharm geprobeerd het bestand [iso-8859-1.txt] te openen als UTF-8. Het programma kan zien dat het bestand [1] niet hetzelfde is als UTF-8. Het stelt daarom voor om het bestand opnieuw te laden in een andere codering:

- in [3-5]: het bestand wordt opnieuw geladen met behulp van een ISO-8859-1-codering;

- naar [6], hetzelfde bestand maar weergegeven met een andere codering;
Als we teruggaan naar de projectinstellingen:

- zien we dat Pycharm bij [6-7] heeft genoteerd dat het bestand [iso-8859-1.txt] moest worden geopend met de codering ISO-8859-1. Dit is dus een uitzondering op de regel [5];
7.4. Script [json_01]: beheer van een bestand jSON
JSON staat voor JavaScript Object Notation. Zoals de naam al aangeeft, is dit een tekstuele weergavemodus voor objecten in de programmeertaal JavaScript. We zullen het hier gebruiken met Python-objecten.
Het bestand jSON, dat wordt beheerd door [data/in.json], ziet er als volgt uit:

- In [2] zien we dat de tekstinhoud van het bestand [in.json] een Python-woordenboek zou kunnen voorstellen. PyCharm heeft deze tekst opgemaakt (Ctrl-Alt-L), maar zelfs als deze op één regel zou staan, zou dat niets veranderen. De vorm van de tekst doet er niet toe, zolang deze syntactisch gezien een Python-object vertegenwoordigt;
Het script [json-01] laat zien hoe dit bestand kan worden gebruikt:
# imports
import codecs
import json
import sys
# een bestand jSON lezen/schrijven
inFile=None
outFile=None
try:
# het bestand jSON openen in leesmodus
inFile = codecs.open("./data/in.json", "r", "utf8")
# de inhoud overbrengen naar een woordenboek
data = json.load(inFile)
# de ingelezen gegevens weergeven
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])}")
# het woordenboek [data] naar een JSON-bestand overbrengen
outFile = codecs.open("./data/out.json", "w", "utf8")
json.dump(data, outFile)
except BaseException as erreur:
# de fout wordt weergegeven en het programma wordt afgesloten
print(f"L'erreur suivante s'est produite : {erreur}")
sys.exit()
finally:
# bestanden sluiten indien ze geopend zijn
if inFile:
inFile.close()
if outFile:
outFile.close()
Opmerkingen
- regel 3: om JSON te verwerken, importeren we de module [json];
- regel 11: we gaan jSON-bestanden verwerken die zijn gecodeerd in UTF-8. Hier openen we het bestand [data/in.json] met de module [codecs];
- regel 13: de methode [json.load] leest de inhoud van het bestand jSON en slaat deze op in de variabele [data]. Het type van deze variabele is hier een woordenboek;
- regels 15-18: om aan te tonen dat we inderdaad een Python-woordenboek hebben verkregen, geven we enkele elementen daarvan weer;
- regels 20-21: we voeren de omgekeerde bewerking uit: het woordenboek [data] wordt met behulp van de methode [json.dump] opgeslagen in een bestand met de naam UTF-8;
- regels 22-25: afhandeling van een eventuele uitzondering;
- regels 26-31: in alle gevallen, of er nu een fout is opgetreden of niet, worden de bestanden die mogelijk zijn geopend, gesloten;
Resultaten
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
- de regels 2-4 laten zien dat het woordenboek in het bestand jSON correct is opgehaald;
Laten we nu eens kijken naar de inhoud van het bestand [data/out.json]:

De tekst van het bestand staat op één regel. PyCharm herkent echter de bestanden jSON en deze kunnen worden opgemaakt, net als Python-bestanden en andere bestanden, met Ctrl-Alt-L. We krijgen dan het volgende:

7.5. Script [json_02]: beheer van jSON-bestanden die zijn gecodeerd in UTF-8
Een jSON-bestand dat is gecodeerd in UTF-8 kan twee vormen aannemen:
# imports
import codecs
import json
import sys
# woordenboek
data = {'marié': 'oui', 'impôt': 1340}
# een bestand schrijven jSON
out_file1 = None
out_file2 = None
try:
# het woordenboek [data] overbrengen naar een JSON-bestand
out_file1 = codecs.open("./data/out1.json", "w", "utf8")
json.dump(data, out_file1, ensure_ascii=True)
# het woordenboek [data] naar een JSON-bestand overbrengen
out_file2 = codecs.open("./data/out2.json", "w", "utf8")
json.dump(data, out_file2, ensure_ascii=False)
except BaseException as erreur:
# de fout wordt weergegeven en het programma wordt afgesloten
print(f"L'erreur suivante s'est produite : {erreur}")
sys.exit()
finally:
# bestanden sluiten indien ze geopend zijn
if out_file1:
out_file1.close()
if out_file2:
out_file2.close()
…
- in dit script wordt het woordenboek [data] (regel 7) naar twee bestanden jSON (regels 14, 17) geschreven;
- regels 14, 17: in beide gevallen wordt een tekstbestand UTF-8 aangemaakt;
- regels 15: bij het schrijven van het woordenboek wordt de parameter met de naam [ensure_ascii=True] gebruikt;
- regels 18: bij het schrijven van het woordenboek wordt de parameter met de naam [ensure_ascii=False] gebruikt;
Dit zijn de twee verkregen bestanden:

- in het bestand [out1.json] zijn de tekens met accenten vervangen door een reeks tekens die hun code UTF-8 weergeven. Men zegt soms dat ze zijn ‘geëscape’. Technisch gezien vindt men in het binaire bestand [out1.json] voor het teken ‘é’ uit [marié] achtereenvolgens de binaire codes UTF-8 van de 6 tekens [\u00e9];
- in het bestand [out2.json] zijn de tekens met accenten ongewijzigd gelaten. Dit betekent dat deze tekens in het binaire bestand van [out2.json] worden weergegeven door hun binaire code UTF-8 (dus slechts één code UTF-8 in plaats van 6 voor out1). Voor het teken é van [marié] vinden we dus de binaire code [00e9] over 4 bytes;
- het is de waarde van de parameter [ensure_ascii] van de methode [json.dump] die bepaalt welk formaat wordt gebruikt;
Sommige toepassingen gebruiken ‘escaped’ UTF-8 voor hun jSON-bestanden. In dat geval moet de waarde [ensure_ascii=True] worden gebruikt. Deze waarde is in feite de standaardwaarde. Als de parameter [ensure_ascii] dus niet wordt gebruikt, wordt er gewerkt met 'escaped' jSON- en UTF-8-bestanden.
Het script gaat als volgt verder:
# imports
import codecs
import json
import sys
# woordenboek
data = {'marié': 'oui', 'impôt': 1340}
…
# bestanden opnieuw inlezen jSON
in_file1 = None
in_file2 = None
try:
# overdracht van bestand jSON 1 naar een woordenboek
in_file1 = codecs.open("./data/out1.json", "r", "utf8")
dico1 = json.load(in_file1)
# weergave
print(f"dico1={dico1}")
# overzetten van het bestand jSON 2 naar een woordenboek
in_file2 = codecs.open("./data/out2.json", "r", "utf8")
dico2 = json.load(in_file2)
# weergave
print(f"dico2={dico2}")
except BaseException as erreur:
# de fout wordt weergegeven en het programma wordt afgesloten
print(f"L'erreur suivante s'est produite : {erreur}")
sys.exit()
finally:
# bestanden sluiten indien ze geopend zijn
if in_file1:
in_file1.close()
if in_file2:
in_file2.close()
Opmerkingen
- regels 11-34: het inlezen van de twee bestanden [out1.json, out2.json] en het weergeven van het in elk geval ingelezen woordenboek;
Resultaten
Verrassend genoeg blijkt dat het niet nodig was om aan de functie [json.load] (regels 17, 22) het coderingstype (al dan niet met escape-tekens) van de te lezen tekenreeks jSON op te geven. In beide gevallen wordt het juiste woordenboek opgehaald.