Bestandbewerkingen in Python

Bestandbewerkingen in Python

Dit artikel legt bestandbewerkingen in Python uit.

Met 'veiligheid', 'efficiëntie' en 'leesbaarheid' in gedachten legt deze gids stap voor stap de concepten uit, van de basis tot praktische technieken.

YouTube Video

Bestandbewerkingen in Python

Bestandsbewerkingen zijn een essentiële basisvaardigheid, van kleine scripts tot grootschalige applicaties.

Bestanden openen en sluiten

Laten we eerst voorbeelden bekijken van het lezen en schrijven van tekstbestanden. Het gebruik van de with-instructie (contextmanager) zorgt ervoor dat het bestand correct wordt gesloten.

De volgende code opent een tekstbestand, leest de inhoud en verwerkt deze regel voor regel.

1# Open and read a text file safely using a context manager.
2with open("example.txt", "r", encoding="utf-8") as f:
3    # Read all lines lazily (as an iterator) and process each line.
4    for line in f:
5        print(line.rstrip())
  • Door expliciet encoding="utf-8" op te geven, worden platformafhankelijke problemen verminderd.
  • Zelfs bij grote bestanden is for line in f geheugen-efficiënt.

Schrijven (overschrijven en toevoegen)

Let goed op de modus bij het schrijven naar bestanden. w is om te overschrijven, a is om toe te voegen. Gebruik de with-instructie ook bij het schrijven.

De volgende code laat basisvoorbeelden zien van overschrijven en toevoegen.

1# Write (overwrite) to a file.
2with open("output.txt", "w", encoding="utf-8") as f:
3    f.write("First line\n")
4    f.write("Second line\n")
5
6# Append to the same file.
7with open("output.txt", "a", encoding="utf-8") as f:
8    f.write("Appended line\n")
  • Deze code schrijft tekst naar output.txt en voegt daarna meer tekst toe aan hetzelfde bestand. De "w"-modus overschrijft het bestand, terwijl de "a"-modus een nieuwe regel aan het einde van de bestaande inhoud toevoegt.
  • Als je moet flushen, roep dan flush() aan, maar meestal wordt de inhoud automatisch geflusht als de context eindigt.
  • Als meerdere processen of threads gelijktijdig kunnen schrijven, moet u exclusieve controle overwegen, zoals bestandsvergrendeling.

Binaire gegevens lezen en schrijven

Afbeeldingen en gecomprimeerde bestanden worden verwerkt in binaire modus (rb of wb). In tegenstelling tot tekstmodus wordt encoding genegeerd.

De volgende code leest een binair bestand en kopieert het naar een ander bestand in binaire modus.

1# Read and write binary files.
2with open("image.png", "rb") as src:
3    data = src.read()
4
5with open("copy.png", "wb") as dst:
6    dst.write(data)
  • Vermijd bij grote binaire bestanden het in één keer lezen met read(); lezen en schrijven in stukken is geheugen-efficiënter.

Voorbeeld van grote bestanden in stukken verwerken

Lees en schrijf enorme bestanden die niet in het geheugen passen in stukken met een vaste grootte. Aangezien het I/O-afhankelijk is, is het handig om de buffergrootte aan te passen.

De volgende code kopieert een bestand in blokken van 64KB. Dit werkt snel terwijl het geheugengebruik laag blijft.

1# Copy a large file in chunks to avoid using too much memory.
2CHUNK_SIZE = 64 * 1024  # 64 KB
3
4with open("large_source.bin", "rb") as src,      open("large_dest.bin", "wb") as dst:
5    while True:
6        chunk = src.read(CHUNK_SIZE)
7        if not chunk:
8            break
9        dst.write(chunk)
  • Je kunt de blokgrootte aanpassen aan de kenmerken van je netwerk of schijf. Op SSD's werkt een iets grotere blokgrootte vaak beter.

Voorbeeld van het gebruik van het buffering-argument

Door het argument buffering op te geven in de functie open(), kun je de buffergrootte die intern door Python wordt gebruikt, beheren. Hierdoor kun je de efficiëntie van input/output verder optimaliseren.

 1# Explicitly set the internal buffer size to 128 KB for faster I/O.
 2CHUNK_SIZE = 64 * 1024
 3BUFFER_SIZE = 128 * 1024  # 128 KB internal buffer
 4
 5with open("large_source.bin", "rb", buffering=BUFFER_SIZE) as src,      open("large_dest.bin", "wb", buffering=BUFFER_SIZE) as dst:
 6    while True:
 7        chunk = src.read(CHUNK_SIZE)
 8        if not chunk:
 9            break
10        dst.write(chunk)
  • Als je de waarde van buffering op 0 zet, wordt I/O uitgevoerd zonder buffering; 1 schakelt line-buffering in, terwijl waarden van 2 of hoger een buffer van de opgegeven grootte in bytes gebruiken.
  • Over het algemeen is de standaardwaarde voldoende omdat het besturingssysteem efficiënt omgaat met caching, maar het aanpassen van deze parameter kan effectief zijn voor zeer grote bestanden of speciale apparaten.

Moderne bestandsmanipulatie met Pathlib

De standaardmodule pathlib maakt padbeheer intuïtiever. Het verbetert de leesbaarheid en veiligheid vergeleken met stringpaden.

 1from pathlib import Path
 2
 3path = Path("data") / "notes.txt"
 4
 5# Ensure parent directory exists.
 6path.parent.mkdir(parents=True, exist_ok=True)
 7
 8# Write and read using pathlib.
 9path.write_text("Example content\n", encoding="utf-8")
10content = path.read_text(encoding="utf-8")
11print(content)
  • Deze code laat zien hoe je een map maakt met pathlib, naar een tekstbestand schrijft en de inhoud leest. Met een Path-object kun je paden op een intuïtieve en veilige manier beheren.
  • Path heeft handige API's zoals iterdir() en glob(). Je kunt code schrijven zonder je zorgen te maken over padseparators tussen verschillende besturingssystemen.

Tijdelijke bestanden en mappen (tempfile)

Tijdelijke bestanden kunnen veilig worden aangemaakt met tempfile. Dit voorkomt beveiligingsproblemen door race-omstandigheden en naamconflicten.

De volgende code laat een voorbeeld zien van het aanmaken van tijdelijke gegevens met een tijdelijk bestand.

 1import tempfile
 2
 3# Create a temporary file that is deleted on close.
 4with tempfile.NamedTemporaryFile(
 5    mode="w+",
 6    encoding="utf-8",
 7    delete=True
 8) as tmp:
 9    tmp.write("temporary data\n")
10    tmp.seek(0)
11    print(tmp.read())
12
13# tmp is deleted here
  • Deze code maakt een tijdelijk bestand aan, schrijft en leest gegevens en verwijdert het automatisch zodra het with-blok eindigt. Met tempfile.NamedTemporaryFile kun je tijdelijke bestanden veilig en zonder conflicten beheren. Omdat delete=True is opgegeven, wordt het bestand automatisch verwijderd.
  • Op Windows kun je het bestand mogelijk niet direct openen vanuit een andere handle, dus kun je delete=False instellen en de verwijdering zelf beheren.

shutil: Hoog-niveau bewerkingen voor kopiëren, verplaatsen en verwijderen

Recursief kopiëren, verplaatsen en verwijderen van bestanden en mappen is eenvoudig met shutil.

 1import shutil
 2
 3# Copy a file preserving metadata.
 4shutil.copy2("source.txt", "dest.txt")
 5
 6# Move a file or directory.
 7shutil.move("old_location", "new_location")
 8
 9# Remove an entire directory tree (use with caution).
10shutil.rmtree("some_directory")
  • shutil.copy2 kopieert ook metadata (zoals de wijzigingstijd). move zal proberen het bestand te verplaatsen, zelfs als een rename niet kan worden gebruikt.
  • rmtree is een gevaarlijke operatie; bevestig en maak altijd een back-up voordat je verwijdert.

Bestandsmetadata (os.stat) en rechtenbeheer

Bestandsgrootte, wijzigingstijd en rechten kunnen worden gelezen en aangepast met os en stat.

De volgende code haalt basisinformatie over bestanden op met os.stat en wijzigt rechten met os.chmod.

 1import os
 2import stat
 3from datetime import datetime
 4
 5st = os.stat("example.txt")
 6print("size:", st.st_size)
 7print("modified:", datetime.fromtimestamp(st.st_mtime))
 8
 9# Make file read-only for owner.
10os.chmod("example.txt", stat.S_IREAD)
  • Rechten werken anders op POSIX- en Windows-systemen. Als platformonafhankelijke compatibiliteit belangrijk is, gebruik dan high-level API's of voeg voorwaardelijke afhandeling toe.

Bestandsvergrendeling (exclusieve controle) — Verschillen tussen Unix en Windows

Exclusieve controle is nodig wanneer meerdere processen gelijktijdig toegang hebben tot hetzelfde bestand. UNIX gebruikt fcntl en Windows gebruikt msvcrt.

De volgende code gebruikt fcntl.flock onder UNIX-systemen om een exclusieve vergrendeling te verkrijgen bij het schrijven.

 1# Unix-style file locking example
 2import fcntl
 3
 4with open("output.txt", "a+", encoding="utf-8") as f:
 5    fcntl.flock(f, fcntl.LOCK_EX)
 6    try:
 7        f.write("Safe write\n")
 8        f.flush()
 9    finally:
10        fcntl.flock(f, fcntl.LOCK_UN)
  • Deze code verkrijgt een exclusieve vergrendeling met fcntl.flock op UNIX-achtige systemen om veilig te schrijven en gelijktijdige schrijfacties te voorkomen. Geef de vergrendeling altijd vrij na verwerking, zodat andere processen toegang krijgen.
  • Gebruik op Windows msvcrt.locking(). Overweeg voor gebruik op een hoger niveau externe bibliotheken zoals portalocker of filelock.

Atomaire schrijfpatronen voor bestanden

Om beschadiging van bestanden tijdens updates te voorkomen, schrijf eerst naar een tijdelijk bestand en vervang het bij succes met os.replace.

Schrijven naar een tijdelijk bestand en daarna vervangen voorkomt beschadiging bij een crash.

 1import os
 2from pathlib import Path
 3import tempfile
 4
 5def atomic_write(path: Path, data: str, encoding="utf-8"):
 6    # Write to a temp file in the same directory and atomically replace.
 7    dirpath = path.parent
 8    with tempfile.NamedTemporaryFile(
 9        mode="w",
10        encoding=encoding,
11        dir=str(dirpath),
12        delete=False
13    ) as tmp:
14        tmp.write(data)
15        tmp.flush()
16        tempname = tmp.name
17
18    # Atomic replacement (on most OSes)
19    os.replace(tempname, str(path))
20
21# Usage
22atomic_write(Path("config.json"), '{"key":"value"}\n')

os.replace voert een atomaire vervanging uit binnen hetzelfde bestandssysteem. Let op: atomiciteit is niet gegarandeerd tussen verschillende aankoppelingen.

Snelle toegang met mmap (voor grootschalige data)

Voor willekeurige toegang tot grote bestanden verbetert mmap de I/O-prestaties. Dit betreft vooral binaire bewerkingen.

De volgende code gebruikt geheugenmapping van een bestand en leest of schrijft specifieke groepjes bytes. Wees voorzichtig bij het wijzigen van de bestandsgrootte.

1import mmap
2
3with open("data.bin", "r+b") as f:
4    mm = mmap.mmap(f.fileno(), 0)  # map entire file
5    try:
6        print(mm[:20])  # first 20 bytes
7        mm[0:4] = b"\x00\x01\x02\x03"  # modify bytes
8    finally:
9        mm.close()
  • Deze code koppelt een binair bestand aan het geheugen met mmap en voert directe lees/schrijfoperaties op byteniveau uit. Geheugenmapping maakt snelle willekeurige toegang tot grote datasets mogelijk.
  • mmap is efficiënt, maar verkeerd gebruik kan leiden tot inconsistenties in de data. Roep flush() aan om indien nodig te synchroniseren.

CSV / JSON / Pickle: Lezen en schrijven per formaat

Specifieke dataformaten hebben aparte modules. Gebruik csv voor CSV, json voor JSON en pickle om Python-objecten op te slaan.

De volgende code geeft basisvoorbeelden van het lezen en schrijven van CSV en JSON, en het gebruik van Pickle. Omdat Pickle willekeurige code kan uitvoeren, laad geen data van onbetrouwbare bronnen.

 1import csv
 2import json
 3import pickle
 4
 5# CSV write
 6with open("rows.csv", "w", encoding="utf-8", newline="") as f:
 7    writer = csv.writer(f)
 8    writer.writerow(["name", "age"])
 9    writer.writerow(["Alice", 30])
10
11# JSON write
12data = {"items": [1, 2, 3]}
13with open("data.json", "w", encoding="utf-8") as f:
14    json.dump(data, f, ensure_ascii=False, indent=2)
15
16# Pickle write (only for trusted environments)
17obj = {"key": "value"}
18with open("obj.pkl", "wb") as f:
19    pickle.dump(obj, f)
  • Het is aan te raden newline="" voor CSV te specificeren om extra lege regels op Windows te voorkomen. Met ensure_ascii=False blijven UTF-8-tekens leesbaar in JSON.

Direct lezen en schrijven van gecomprimeerde bestanden (gzip / bz2 / zipfile)

Direct werken met gzip en zip kan schijfruimte besparen. De standaardbibliotheek bevat de bijbehorende modules.

De volgende code is een eenvoudig voorbeeld van het lezen en schrijven van gzip-bestanden als tekst.

1import gzip
2
3# Write gzipped text.
4with gzip.open("text.gz", "wt", encoding="utf-8") as f:
5    f.write("Compressed content\n")
6
7# Read gzipped text.
8with gzip.open("text.gz", "rt", encoding="utf-8") as f:
9    print(f.read())
  • Er is een afweging tussen compressieverhouding en snelheid, afhankelijk van het compressieniveau en het formaat.

Beveiligings- en kwetsbaarheidsmaatregelen

De volgende punten kunnen worden overwogen voor beveiligings- en kwetsbaarheidsmaatregelen.

  • Gebruik geen onbetrouwbare invoer direct in bestandsnamen of paden.
  • Gebruik Pickle alleen met vertrouwde bronnen.
  • Beperk uitvoeringsrechten tot een minimum en geef alleen de noodzakelijke rechten aan processen die bestanden verwerken.
  • Gebruik tijdelijke bestanden met tempfile en sla geen gewone bestanden op in openbare mappen.

Bij het gebruik van gebruikersinvoer in bestandspaden is normalisatie en validatie vereist. Gebruik bijvoorbeeld Path.resolve() en controleer bovenliggende mappen.

 1from pathlib import Path
 2
 3def safe_path(base_dir: Path, user_path: str) -> Path:
 4    candidate = (base_dir / user_path).resolve()
 5    if base_dir.resolve() not in candidate.parents and base_dir.resolve() != candidate:
 6        raise ValueError("Invalid path")
 7    return candidate
 8
 9# Usage
10base = Path("/srv/app/data")
11
12# Raises an exception: attempted path traversal outside `base`
13safe = safe_path(base, '../etc/passwd')
  • Wees extra voorzichtig bij het gebruik van externe invoer als bestandspad in webapps of publieke API's.

Overzicht van veelgebruikte patronen

  • Gebruik altijd de with-instructie (automatisch sluiten).
  • Specificeer altijd expliciet encoding voor tekstgegevens.
  • Lees en schrijf grote bestanden in stukken.
  • Voer bestandsvergrendeling in voor gedeelde bronnen.
  • Gebruik voor kritieke updates het atomische patroon van 'schrijven naar een tijdelijk bestand → os.replace'.
  • Bevestig altijd en maak een back-up voordat je gevaarlijke handelingen uitvoert (zoals verwijderen of overschrijven).
  • Normaliseer en valideer wanneer je externe invoer als bestands­pad gebruikt.

Samenvatting

Voor bestandsbewerkingen is het belangrijk om veilige en betrouwbare methoden te gebruiken, zoals het gebruik van de with-statement, het expliciet specificeren van encodering en atomisch schrijven. Voor grootschalige verwerking of gelijktijdige toegang is het noodzakelijk om vergrendelings- en logbeheersystemen te implementeren om gegevensbeschadiging en conflicten te voorkomen. Het balanceren van efficiëntie en veiligheid is de sleutel tot betrouwbare bestandsbewerkingen.

Je kunt het bovenstaande artikel volgen met Visual Studio Code op ons YouTube-kanaal. Bekijk ook het YouTube-kanaal.

YouTube Video