Beetje Coderen

Les 4

Bestanden en paden met pathlib

Bestanden lezen, schrijven en terugvinden met pathlib.

⏱ 45–60 minuten leestijd + oefenen

🎯 Na deze les kun je

  • paden platformonafhankelijk opbouwen met pathlib
  • bestanden lezen en schrijven met with
  • mappen doorzoeken met glob

Scripts die met bestanden werken, lopen vaak vast op paden: een backslash op Windows en een slash op Linux, een map die nog niet bestaat, een bestandsnaam met een spatie, of een script dat alleen werkt als je het vanuit precies de goede map start. De module pathlib maakt van een pad een object met handige methodes, zodat je minder met strings hoeft te knippen en plakken en je code op elk besturingssysteem werkt.

Path-objecten

from pathlib import Path

p = Path("data") / "2026" / "verkoop.csv"
print(p)
print(p.name)
print(p.stem)
print(p.suffix)
print(p.parent)
data/2026/verkoop.csv
verkoop.csv
verkoop
.csv
data/2026

De /-operator plakt paddelen aan elkaar met het juiste scheidingsteken voor je systeem. Op Windows print p daarom data\2026\verkoop.csv. Je hoeft dus nooit zelf slashes in strings te plakken.

Andere handige eigenschappen en methodes:

Code Betekenis
Path.cwd() de huidige werkmap
Path.home() je thuismap
p.exists(), p.is_file(), p.is_dir() bestaat het, en is het een bestand of map?
p.resolve() het volledige, absolute pad
p.with_suffix(".txt") hetzelfde pad met een andere extensie
p.with_name("x.csv") dezelfde map, andere bestandsnaam

Lezen en schrijven

Voor kleine bestanden zijn er twee korte methodes:

from pathlib import Path

notitie = Path("notitie.txt")
notitie.write_text("Boodschappen:\n- melk\n- brood\n", encoding="utf-8")

tekst = notitie.read_text(encoding="utf-8")
print(tekst.splitlines())
print(notitie.stat().st_size, "bytes")
['Boodschappen:', '- melk', '- brood']
29 bytes

write_text maakt het bestand aan of overschrijft het. Geef altijd encoding="utf-8" op (les 3).

Voor grotere bestanden, of om regel voor regel te verwerken, gebruik je open met with. Een Path heeft er zelf een:

from pathlib import Path

log = Path("app.log")
log.write_text("INFO start\nERROR geen verbinding\nINFO klaar\n", encoding="utf-8")

with log.open(encoding="utf-8") as f:
    for regel in f:
        if regel.startswith("ERROR"):
            print(regel.rstrip())
ERROR geen verbinding

Zo staat nooit het hele bestand tegelijk in het geheugen. with sluit het bestand netjes, ook als er een fout optreedt (meer daarover in les 21). rstrip() haalt de regeleinde-\n weg.

Toevoegen in plaats van overschrijven: p.open("a", encoding="utf-8").

Mappen maken

from pathlib import Path

uitvoer = Path("rapporten") / "2026" / "oktober"
uitvoer.mkdir(parents=True, exist_ok=True)
(uitvoer / "samenvatting.txt").write_text("klaar\n", encoding="utf-8")
print(uitvoer.is_dir())
True
  • parents=True maakt ook de tussenliggende mappen (zoals mkdir -p).
  • exist_ok=True voorkomt een fout als de map al bestaat.

Zoeken met glob en rglob

from pathlib import Path

for naam in ["a.csv", "b.txt", "oud/c.csv", "oud/2025/d.csv"]:
    p = Path("project") / naam
    p.parent.mkdir(parents=True, exist_ok=True)
    p.touch()

project = Path("project")
print(sorted(p.name for p in project.glob("*.csv")))
print(sorted(p.name for p in project.rglob("*.csv")))
print(sorted(str(p) for p in project.glob("*/*.csv")))
['a.csv']
['a.csv', 'c.csv', 'd.csv']
['project/oud/c.csv']
  • glob("*.csv") zoekt alleen in de map zelf.
  • rglob("*.csv") zoekt ook in alle submappen (recursief).
  • Het resultaat is een generator (les 14); met sorted of list krijg je een lijst. De volgorde van glob is niet gegarandeerd, daarom sorteren we.

Paden ten opzichte van je script

Een veelvoorkomend probleem: je script leest config.json, en dat werkt als je het start vanuit de projectmap, maar niet als je het vanuit een andere map start. Relatieve paden zijn namelijk relatief ten opzichte van de huidige werkmap, niet van het script.

Oplossing: bouw het pad vanuit de locatie van het script zelf.

from pathlib import Path

HIER = Path(__file__).resolve().parent
config = HIER / "config.json"

__file__ is het pad van het huidige bestand. Zo werkt het script, waar je het ook start.

Uitgewerkt voorbeeld: foto's sorteren per jaar

In een map staan foto's met de datum in de naam, zoals 2025-07-14_strand.jpg. Het script verplaatst ze naar een map per jaar. Voorspel welke mappen er ontstaan.

from pathlib import Path

fotos = Path("fotos")
fotos.mkdir(exist_ok=True)
for naam in ["2025-07-14_strand.jpg", "2026-01-02_sneeuw.JPG", "notities.txt", "2025-12-24_kerst.jpg"]:
    (fotos / naam).touch()

for foto in sorted(fotos.iterdir()):
    if foto.suffix.lower() != ".jpg":
        continue
    jaar = foto.name[:4]
    doelmap = fotos / jaar
    doelmap.mkdir(exist_ok=True)
    foto.rename(doelmap / foto.name)

for p in sorted(fotos.rglob("*")):
    print(p.relative_to(fotos))
2025
2025/2025-07-14_strand.jpg
2025/2025-12-24_kerst.jpg
2026
2026/2026-01-02_sneeuw.JPG
notities.txt
  • iterdir() geeft alles in de map, één niveau diep.
  • suffix.lower() vangt ook .JPG.
  • rename verplaatst het bestand. Bestaat het doel al, dan wordt het op Linux en macOS zonder waarschuwing overschreven; op Windows krijg je een fout. Controleer in echte scripts eerst met exists().
  • We maken de lijst met sorted(...) vóór het verplaatsen, zodat we niet door een map lopen die we tegelijk veranderen.

Veelgemaakte fouten

  • Paden aan elkaar plakken met + en "/" in plaats van / tussen Path-objecten.
  • Vergeten dat relatieve paden afhangen van de werkmap; gebruik Path(__file__).resolve().parent.
  • write_text op een bestaand bestand: de oude inhoud is weg.
  • FileNotFoundError bij schrijven: de map bestaat nog niet. Gebruik mkdir(parents=True, exist_ok=True).

Oefenen

Oefening 1. Gegeven p = Path("verslagen/2026/q3-cijfers.xlsx"). Wat geven p.stem, p.suffix, p.parent.name en p.with_suffix(".csv")?

Modelantwoord oefening 1
from pathlib import Path
p = Path("verslagen/2026/q3-cijfers.xlsx")
print(p.stem, p.suffix, p.parent.name, p.with_suffix(".csv"))
q3-cijfers .xlsx 2026 verslagen/2026/q3-cijfers.csv

Oefening 2. Schrijf een functie tel_regels(map) die van elk .py-bestand in een map en alle submappen het aantal regels print.

Modelantwoord oefening 2
from pathlib import Path

def tel_regels(map):
    for bestand in sorted(Path(map).rglob("*.py")):
        with bestand.open(encoding="utf-8") as f:
            aantal = sum(1 for _ in f)
        print(f"{aantal:>5}  {bestand}")

sum(1 for _ in f) telt de regels zonder het hele bestand in het geheugen te laden.

Oefening 3. Waarom faalt open("data/uitvoer.txt", "w") met FileNotFoundError, terwijl je toch een nieuw bestand wilt maken?

Modelantwoord oefening 3

De map data bestaat niet. Python maakt wel een nieuw bestand aan, maar geen mappen. Oplossing:

from pathlib import Path
uit = Path("data/uitvoer.txt")
uit.parent.mkdir(parents=True, exist_ok=True)
uit.write_text("...", encoding="utf-8")

Lab: downloads opruimen

  1. In de map downloads van het lab staan tientallen bestanden door elkaar, ook in submappen.
  2. Schrijf in opruimen.py de functie ruim_op(bron, doel) die alle bestanden (ook uit submappen) naar doel/<extensie>/ verplaatst, bijvoorbeeld doel/pdf/factuur.pdf. Extensies in kleine letters; bestanden zonder extensie gaan naar doel/overig/.
  3. Bestaat een bestand met die naam al in de doelmap, voeg dan _1, _2 enzovoort toe aan de naam (vóór de extensie).
  4. De functie geeft een dictionary terug met per extensie het aantal verplaatste bestanden.
  5. Controleer met beetje check python-les4.

Samenvatting

  • Path maakt van een pad een object; plak paden met /, en gebruik .name, .stem, .suffix en .parent.
  • read_text en write_text voor kleine bestanden, p.open() met with om regel voor regel te werken. Altijd encoding="utf-8".
  • mkdir(parents=True, exist_ok=True) maakt mappen; iterdir, glob en rglob zoeken bestanden.
  • Maak paden onafhankelijk van de werkmap met Path(__file__).resolve().parent.

In les 5 bekijk je lijsten, tuples en sets van dichterbij.

Lab

Fotomap opruimen: bestanden hernoemen op datum

💻 Dit lab doe je op je eigen computer met Docker. Start de lab-container met (geeft de opdracht een eigen startcommando, gebruik dan dat):

$ docker run -it --rm registry.wizg.it/webhosting/beetje-labs:python-les4

Volg daarna de stappen van de opdracht hierboven. Nog geen Docker? Zie les 14 van Een beetje Linux & Docker. Voor dit lab is er geen live lab in de browser.

Klaar? Controleer je werk met:

$ beetje check python-les4

📝 Quiz: bestanden en paden met pathlib

5 vragen · 70% nodig om te slagen

1. Wat is `Path("data/verkoop.csv").stem`?
2. Wat is het verschil tussen glob("*.txt") en rglob("*.txt")?
3. Wat doet `mkdir(parents=True, exist_ok=True)`?
4. Je script vindt config.json alleen als je het vanuit de projectmap start. Hoe los je dat op?
5. Wat gebeurt er met de oude inhoud als je write_text gebruikt op een bestaand bestand?