Hoofdstuk 10 - Bestanden en Uitzonderingen

01 - Een bestand lezen
  • Veelgebruikte tekstgebaseerde bestandsformaten die Python direct kan inlezen zijn:
    • .txt
    • .csv
    • .json
    • .xml
    • .html
  • .pdf (vereist externe modules zoals PyPDF2 of pdfplumber)
  • Je kunt van een .txt file de eerste regel inlezen maar ook de gehele file
  • In het onderstaande voorbeeld wordt de gehele file pi_digits.txt geopend
with open("pi_digits.txt") as file_object:
    inhoud = file_object.read()

print(inhoud)
3.1415926535
  8979323846
  2643383279
  • Als je in je eigen editor de file wilt openen, plaats pi_digits.txt dan in dezelfde folder als je Python-file
  • Je kunt de file ook openen met de editor op deze pagina
  • De open()-functie heeft 1 argument nodig: de filenaam, en retourneert een object dat het bestand vertegenwoordigt
  • Python slaat dit object op in file_object
  • Het keyword with sluit het bestand zodra het niet meer nodig is
    • Je kunt het bestand ook zelf sluiten
    • Als het zelf sluiten door een bug mislukt blijft de file open
    • Open bestanden zorgen voor problemen, verlies van data en corrupte files
    • Vertrouw erop dat Python met with zelf het bestand sluit wanneer nodig
  • Zodra er een bestandsobject is met de inhoud van de file gebruik je de .read()-functie om de inhoud op te slaan in een variabele
  • Het bestandsobject blijft beschikbaar binnen de with-constructie. Na het with-blok is het bestandsobject niet meer beschikbaar

Bestandspaden
  • Python zoekt een externe file op de locatie vanwaar je het Python-script uitvoert
  • Je kunt ook relatieve paden gebruiken om elders te zoeken
with open("some_folder/filename.txt") as file_object:
  • Windows-systemen gebruiken een backslash \ in plaats van een slash / in een pad
  • Gebruik in je Python code altijd de gewone slash / (een backslash is om karakters te escapen)
  • Je kunt ook absolute paden gebruiken:
    • /Users/mo6/Server/python/app/static/txt/pi_digits.txt
    • C:/Users/Jan/Documents/Python/app/static/txt/pi_digits.txt

Regel voor regel lezen
  • Je kunt een for-loop gebruiken om een bestandsobject regel voor regel te bekijken
filename = "pi_digits.txt"

with open(filename) as file_object:
    for line in file_object:
        print(line)
  • Een veel voorkomende conventie is om de bestandsnaam op te slaan in een variabele
  • Door with te gebruiken zorgt Python voor het openen en sluiten van de file
  • Heb je lege regels tussen de uitkomstregels gebruik dan de rstrip()-functie
    • print(line.rstrip())

  • Je kunt van een bestand ook een lijst met regels maken
  • De lijst blijft beschikbaar buiten het with-blok
  • Met de functie readlines() kun je elke regel van een tekstfile opslaan in een lijst
filename = "pi_digits.txt"

with open(filename) as file_object:
    lines = file_object.readlines()

for line in lines:
    print(line.rstrip())
3.1415926535
  8979323846
  2643383279


Werken met de inhoud van een bestand
  • Nadat je een lijst hebt gemaakt van elke regel uit een file kun je er goed mee werken
  • Zo kun je bijvoorbeeld alle items uit de lijst plaatsen in één variabele (zonder spaties)
filename = "pi_digits.txt"

with open(filename) as file_object:
    lines = file_object.readlines()

pi_string = ''
for line in lines:
    pi_string += line.strip()

print(pi_string)
print(len(pi_string))
3.141592653589793238462643383279
32

  • Een ingelezen file door Python wordt verwerkt als tekst (string)
  • Gebruik int() of float() om regels om te zetten naar getallen
  • Python kan ook veel grotere files analyseren dan de voorgaande oefeningen met drie regels
  • Bijvoorbeeld de file pi_million.txt met de eerste 1.000.000 cijfers van pi
  • Download deze file (1MB) en probeer in je eigen editor (niet op deze website) onderstaande code
verjaardag = input("Geef je verjaardag op - dd-mm-yy : ")

with open("pi_million.txt", "r", encoding="utf-8") as f:
    pi_cijfers = f.read()

if verjaardag in pi_cijfers:
    print("Ja, je verjaardag komt voor in pi!")
else:
    print("Nee, je verjaardag komt niet voor in deze cijfers van pi.")

  • Argument "r" betekent "read": alleen lezen
  • f is gekozen als variabelenaam en is lekker kort (het staat voor file of file_object)
  • encoding bepaalt hoe tekstbestanden worden opgeslagen en gelezen (koppeling tussen bytes en tekens)
  • Zonder encoding gebruikt Python de standaard-encoding van het besturingssysteem
  • Op macOS & Linux is de standaard bijna altijd UTF-8
  • Op Windows is de standaard meestal géén UTF-8 maar een Windows-codepagina (bijv. cp1252)
  • Daarom is het verstandig om altijd encoding="utf-8" te gebruiken, dat voorkomt platformverschillen
  • UTF-8 is de wereldwijde standaard voor software. Het ondersteunt accenten, emoji en symbolen en is in 99% van alle gevallen de juiste keuze
Besturingssysteem Standaard encoding Opmerking
macOS UTF-8 Python gebruikt UTF-8 als standaard
Linux UTF-8 Vrijwel alle distro’s gebruiken UTF-8
Windows cp1252 (of andere codepage) Niet standaard UTF-8 (kan fouten geven)


02 - Naar een bestand schrijven
  • De eenvoudigste manier om data op te slaan is om die naar een bestand te schrijven
  • Met een extra argument bij de open()-functie kun je data naar een bestand schrijven
filename = "mijn_verhaal.txt"

with open(filename, "w") as file_object:
    file_object.write("Ergens hier ver vandaan...\n")
    file_object.write("Waar paddenstoelen huisjes staan...\n")

Mode Betekenis Lezen Schrijven Moet bestaan? Opmerking
r read ✔︎ ✔︎ Fout als bestand ontbreekt
w write ✔︎ Maakt nieuw of wist alles
a append ✔︎ Voegt toe aan einde
x exclusive create ✔︎ Fout als bestand al bestaat
r+ read/write ✔︎ ✔︎ ✔︎ Moet bestaan
w+ write/read ✔︎ ✔︎ Leegt bestand eerst
a+ append/read ✔︎ ✔︎ Schrijven altijd aan einde
rb wb ab binary-modus ✔︎ ✔︎ Voor pdf, afbeeldingen, audio
  • Als je het modus-argument weglaat is de default modus r / read-only
  • Kijk uit met het maken van bestanden w-modus i.v.m. het verwijderen van eerdere content
  • Het voorgaande script heeft geen uitvoer maar je kunt de file wel weer inlezen
filename = "mijn_verhaal.txt"

with open(filename, "r") as file_object:
    content = file_object.read()

print(content)
Ergens hier ver vandaan...
Waar paddenstoelen huisjes staan...

  • De file die gemaakt wordt bij het uitvoeren van Python-code op deze website kun je niet terugvinden
  • Probeer de code in je eigen editor om daadwerkelijk een file te kunnen bewaren
  • Python kan alleen strings schrijven naar tekstbestanden. Als je numerieke data in een tekstbestand wilt opslaan, moet je de data eerst met str()-functie omzetten naar string
  • De write()-functie voegt geen enters toe aan de tekst, daar moet je zelf voor zorgen

Iets aan een bestand toevoegen
  • Met de append mode kun je tekst aan een file toevoegen
filename = "mijn_verhaal.txt"

with open(filename, "a") as file_object:
    file_object.write("Daar woont een volkje vrij en blij...\n")
Ergens hier ver vandaan...
Waar paddenstoelen huisjes staan...
Daar woont een volkje vrij en blij...

03 - Uitzonderingen
  • Python gebruikt speciale objecten die exceptions (uitzonderingen) worden genoemd
  • Exceptions worden gebruikt om fouten af te handelen die ontstaan tijdens de uitvoer van een programma
  • Python maakt bij elke fout een uitzonderingsobject
  • Handel je uitzonderingsobject niet af dan ziet de gebruiker een foutmelding (traceback)
  • Als je uitzonderingsobjecten afhandelt in de code kan het programma doorgaan
  • Uitzonderingen worden afgehandeld met try-except-blokken
  • Met een try-except-blok kan er iets mis gaan maar kun je de gebruiker een mooie melding geven
print(5/0)
ZeroDivisionError: division by zero
  • Je kunt geen getal delen door 0, dat geeft altijd een ZeroDivisionError
  • De ZeroDivisionError is een uitzonderingsobject
try:
    print(5/0)
except ZeroDivisionError:
    print("Je kunt een getal niet delen door 0!")
Je kunt een getal niet delen door 0!

Uitzonderingenlijst
  • Exception – de algemene basisfout waarvan alle andere fouten erven
  • SyntaxError – er staat een fout in de code (bijv. haakje vergeten)
  • IndentationError – verkeerde inspringing (type van SyntaxError)
  • NameError – je gebruikt een variabele die niet bestaat
  • TypeError – verkeerde soort waarde (bijv. 3 + "abc")
  • ValueError – juiste type, maar onjuiste waarde (bijv. tekst omzetten naar int)
  • IndexError – je probeert buiten de grenzen van een lijst te werken
  • KeyError – een niet-bestaande sleutel in een dictionary
  • AttributeError – een object heeft het gevraagde attribuut niet
  • ZeroDivisionError – delen door nul
  • FileNotFoundError – bestand bestaat niet (subclass van OSError)
  • PermissionError – onvoldoende rechten om een bestand te openen
  • IOError / OSError – algemene fouten bij werken met bestanden en hardware
  • IsADirectoryError – je probeert een directory als bestand te openen
  • NotADirectoryError – een padsegment dat een map zou moeten zijn, is geen map
  • UnicodeDecodeError – verkeerde encoding bij het lezen van tekst
  • UnicodeEncodeError – verkeerde encoding bij schrijven
  • RecursionError – maximale recursiediepte overschreden (functie die zichzelf aanroept)
  • ImportError – een module kan niet gevonden worden
  • ModuleNotFoundError – specifiek geval van ImportError: module bestaat niet
  • RuntimeError – algemene fout tijdens runtime
  • StopIteration – een iterator is “op”
  • KeyboardInterrupt – gebruiker drukte Ctrl+C
  • MemoryError – te weinig geheugen beschikbaar


  • Om een crash te voorkomen handelen we de ZeroDivisionError af
  • Door een else toe te voegen kan de applicatie verder
print("Geef twee getallen op, dan deel ik het eerste getal.")
print("Enter 'q' om te stoppen.")

while True:
    first_number = input("\nEerste nummer: ")
    if first_number == 'q':
        break
    second_number = input("Tweede nummer: ")
    try:
        answer = int(first_number) / int(second_number)
    except ZeroDivisionError:
        print("Je kunt niet door 0 delen!")
    else:
        print(answer)
        +-----------------------------+
        |           try               |
        |   (probeer deze code)       |
        +-------------+---------------+
                      |
                      v
        +-----------------------------+
        |        GING ER EEN FOUT?    |
        +-------------+---------------+
                      |
          +-----------+-----------+
          |                       |
         ja                      nee
          |                       |
          v                       v
+------------------+     +-----------------------+
|      except      |     |         else          |
| code voor fouten |     | code als géén fout    |
+------------------+     +-----------------------+

  • Bij gebruikersinvoer zijn twee fouten echt relevant:
    • ValueError: gebruiker typt iets dat geen getal is
    • ZeroDivisionError: gebruiker deelt door nul
  • Je kunt meerdere except blokken toevoegen
print("Geef twee getallen op, dan deel ik het eerste getal.")
print("Enter 'q' om te stoppen.")

while True:
    first_number = input("\nEerste nummer: ")
    if first_number == 'q':
        break
    second_number = input("Tweede nummer: ")
    try:
        answer = int(first_number) / int(second_number)
    except ZeroDivisionError:
        print("Je kunt niet door 0 delen!")
    except ValueError:
        print("Je moet wel een geldig getal invoeren!")
    else:
        print(answer)

Afhandelen van de FileNotFoundError
filename = 'matrix.txt'

try:
    with open(filename, encoding='utf-8') as f_obj:
        contents = f_obj.read()
except FileNotFoundError as e:
    msg = "Sorry, de file " + filename + " bestaat niet"
    print(msg)
Sorry, de file matrix.txt bestaat niet
  • Het daadwerkelijk foutobject wordt hier e genoemd en kun je printen
  • Na de FileNotFoundError heeft het programma niks meer te doen
  • In het onderstaande voorbeeld wordt string-methode split() gebruikt
  • split() deelt een string bij de spatie en slaat elk deel los in een lijst op
title = "Alice in Wonderland"
print(title.split())
['Alice', 'in', 'Wonderland']
  • Met split() kun je de woorden tellen in een file
filename = 'alice.txt'

try:
    with open(filename, encoding='utf-8') as f_obj:
        contents = f_obj.read()
except FileNotFoundError as e:
    msg = "Sorry, de file " + filename + " bestaat niet"
    print(msg)
else:
    words = contents.split()
    num_words = len(words)
    print("De file " + filename + " heeft ongeveer " + str(num_words) + " woorden")
De file alice.txt heeft ongeveer 133 woorden


Met meerdere bestanden werken
  • Door het vorige voorbeeld in een functie te zetten kunnen we het makkelijk aanroepen op meerdere bestanden
def count_words(filename):
    """Tel ongeveer het aantal woorden in een file."""
    try:
        with open(filename, encoding='utf-8') as f_obj:
            contents = f_obj.read() 
    except FileNotFoundError as e:
        msg = "Sorry, de file " + filename + " bestaat niet"
        print(msg)
    else:
        words = contents.split()
        num_words = len(words)
        print("De file " + filename + " heeft ongeveer " + str(num_words) + " woorden")


filenames = ['alice.txt', 'matrix.txt', 'smurf.txt']
for filename in filenames:
    count_words(filename)
De file alice.txt heeft ongeveer 133 woorden
Sorry, de file matrix.txt bestaat niet
De file smurf.txt heeft ongeveer 85 woorden
  • Maar we kunnen ook de foutmelding stil voorbij laten gaan
def count_words(filename):
    """Tel ongeveer het aantal woorden in een file."""
    try:
        with open(filename, encoding='utf-8') as f_obj:
            contents = f_obj.read() 
    except FileNotFoundError as e:
        pass
    else:
        words = contents.split()
        num_words = len(words)
        print("De file " + filename + " heeft ongeveer " + str(num_words) + " woorden")


filenames = ['alice.txt', 'matrix.txt', 'smurf.txt']
for filename in filenames:
    count_words(filename)
De file alice.txt heeft ongeveer 133 woorden
De file smurf.txt heeft ongeveer 85 woorden
  • Goed geschreven en adequaat geteste code is niet gevoelig voor interne fouten
  • Maar wel wanneer het programma afhankelijk is van zaken van buitenaf
    • Invoer van de gebruiker
    • Bestaan van een bestand
    • Beschikbaarheid van netwerkverbinding
  • Aan jou om te bepalen wanneer je foutmeldingen toont

04 - Data opslaan
  • Een JSON file is mens-leesbaar, waardoor je opgeslagen data makkelijk kunt bekijken en controleren
  • Python kan JSON direct inlezen en opslaan met de ingebouwde json-module
  • JSON werkt met vaste structuren zoals lijsten en dictionaries, die perfect passen bij Python-datatypes
  • Het is ideaal om instellingen (configuraties) van programma’s op te slaan
  • JSON wordt ondersteund door bijna alle programmeertalen, niet alleen Python
  • JSON is perfect voor gegevensuitwisseling tussen verschillende apps en systemen
  • Webapplicaties gebruiken JSON als standaardformaat voor communicatie met servers (API’s)
  • Je kunt JSON makkelijk versturen via internet zonder speciale conversies
  • JSON komt van JavaScript Object Notation en is ontstaan rond 2002
  • JSON vervangt XML, want JSON bleek:
    • Korter
    • Sneller
    • Makkelijker te lezen
    • Makkelijker te parsen
  • Het is de wereldwijde standaard voor communicatie tussen applicaties
import json

data = {
    "event": "F1-2025",
    "points": [423, 421, 410]
}

# JSON wegschrijven naar een virtueel bestand
with open("data.json", "w") as f:
    json.dump(data, f)

# JSON opnieuw inlezen
with open("data.json", "r") as f:
    loaded = json.load(f)
    print(loaded)
{'event': 'F1-2025', 'points': [423, 421, 410]}
  • In deze online omgeving wordt data.json opgeslagen in een virtueel bestandssysteem (in het geheugen)
  • Als je de pagina ververst, ben je dit bestand weer kwijt
  • In je eigen editor wordt data.json echt als bestand op je schijf aangemaakt

Oefening
Quiz

Vraag 1. Wat is het belangrijkste voordeel van een with-blok bij het werken met bestanden?




Klopt. Met een with-blok zorgt Python zelf voor het sluiten van het bestand.
Niet helemaal. Het grote voordeel is dat het bestand automatisch wordt gesloten.

Vraag 2. Wat is het verschil tussen f.read() en f.readlines()?




Juist. read() → één grote string, readlines() → lijst met regels.
Let op. read() geeft een string, readlines() een lijst van strings.

Vraag 3. Wat doet de modus "w" in open("mijn_verhaal.txt", "w")?




Helemaal goed. Met "w" schrijf je, en een bestaand bestand wordt geleegd.
Niet juist. De modus "w" maakt een nieuw bestand of wist eerst alle inhoud.

Vraag 4. Welke van de onderstaande paden is een relatief pad?




Klopt. Dit pad is relatief ten opzichte van de huidige werkmap van het script.
Let op. Een relatief pad begint niet met / of een schijfletter zoals C:.

Vraag 5. Waarom is het verstandig om bij tekstbestanden expliciet encoding="utf-8" te gebruiken?




Goed! UTF-8 is de standaard op de meeste systemen en voorkomt encoding-problemen.
Niet helemaal. De reden is compatibiliteit tussen systemen en goede ondersteuning voor tekens.

Vraag 6. Bij het delen van twee getallen die een gebruiker invoert, welke fouten zijn dan vooral relevant?




Precies. Voor gebruikersinvoer bij deling zijn vooral ValueError en ZeroDivisionError belangrijk.
Let op. Denk aan: verkeerd invoerformaat (ValueError) en delen door nul (ZeroDivisionError).

Vraag 7. Wat doet deze code als "matrix.txt" niet bestaat?

filename = "matrix.txt"

try:
    with open(filename, encoding="utf-8") as f_obj:
        contents = f_obj.read()
except FileNotFoundError:
    print("Sorry, de file", filename, "bestaat niet")



Juist. De FileNotFoundError wordt afgehandeld en er verschijnt een duidelijke melding.
Niet goed. Door het try-except-blok krijgen we een nette tekst in plaats van een crash.

Vraag 8. Wat doet het sleutelwoord pass in een except-blok?

try:
    with open(filename) as f:
        contents = f.read()
except FileNotFoundError:
    pass



Klopt. pass betekent “doe hier niets” en gaat verder met de volgende regel.
Niet helemaal. pass negeert de fout; er wordt geen melding getoond.

Vraag 9. Welke code gebruikt de json-module op de juiste manier om een dictionary naar een bestand te schrijven en weer in te lezen?




Helemaal goed. json.dump schrijft naar een file, json.load leest weer terug.
Let op. Gebruik json.dump(..., f) om naar een bestand te schrijven, en json.load(f) om in te lezen.

Vraag 10. Je gebruikt deze functie om woorden te tellen in meerdere bestanden:

def count_words(filename):
    try:
        with open(filename, encoding="utf-8") as f_obj:
            contents = f_obj.read()
    except FileNotFoundError:
        pass
    else:
        words = contents.split()
        num_words = len(words)
        print("De file", filename, "heeft ongeveer", num_words, "woorden")

filenames = ["alice.txt", "matrix.txt", "smurf.txt"]
for filename in filenames:
    count_words(filename)

Wat gebeurt er als "matrix.txt" niet bestaat?




Juist. Door pass wordt de fout genegeerd en gaat de loop verder met de volgende file.
Niet helemaal. De except-blok met pass zorgt ervoor dat de fout stil wordt overgeslagen.