Les 4
Bestanden en paden met pathlib
Bestanden lezen, schrijven en terugvinden met pathlib.
🎯 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=Truemaakt ook de tussenliggende mappen (zoalsmkdir -p).exist_ok=Truevoorkomt 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
sortedoflistkrijg je een lijst. De volgorde vanglobis 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.renameverplaatst 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 metexists().- 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_textop een bestaand bestand: de oude inhoud is weg.FileNotFoundErrorbij schrijven: de map bestaat nog niet. Gebruikmkdir(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
- In de map
downloadsvan het lab staan tientallen bestanden door elkaar, ook in submappen. - Schrijf in
opruimen.pyde functieruim_op(bron, doel)die alle bestanden (ook uit submappen) naardoel/<extensie>/verplaatst, bijvoorbeelddoel/pdf/factuur.pdf. Extensies in kleine letters; bestanden zonder extensie gaan naardoel/overig/. - Bestaat een bestand met die naam al in de doelmap, voeg dan
_1,_2enzovoort toe aan de naam (vóór de extensie). - De functie geeft een dictionary terug met per extensie het aantal verplaatste bestanden.
- Controleer met
beetje check python-les4.
Samenvatting
Pathmaakt van een pad een object; plak paden met/, en gebruik.name,.stem,.suffixen.parent.read_textenwrite_textvoor kleine bestanden,p.open()metwithom regel voor regel te werken. Altijdencoding="utf-8".mkdir(parents=True, exist_ok=True)maakt mappen;iterdir,globenrglobzoeken 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
Klaar? Controleer je werk met:
$ beetje check python-les4