← Python/Bestanden en I/O
Les 7 van 8
MODULE 11 · 07◷ 30 min

Binaire bestanden byte voor byte behouden

Na deze les kun je
  • Binaire I/O onderscheiden van tekstdecodering
  • Een byteprefix teruggeven en bestanden zonder omzetting kopiëren

Een afbeelding, archief of ontvangen pakketje hoeft geen leesbare tekst te bevatten. Toch wil je het exact kunnen bewaren of kopiëren. Als je bij ieder bestand automatisch UTF8 probeert te lezen, kan je programma geldige binaire gegevens weigeren of onbedoeld veranderen.

Een byte is geen letter

Een bytes-waarde is een onveranderlijke reeks getallen van nul tot en met 255. Eén index levert een integer; een slice levert weer bytes. De weergave met een b voor het aanhalingsteken maakt het type herkenbaar, maar is niet de opgeslagen inhoud zelf.

Een nulbyte is een gewoon onderdeel van zo'n reeks. Hetzelfde geldt voor de byte 255, ook als die op zichzelf geen geldige UTF8-letter vormt. Bij binaire I/O bewaren we zulke waarden zonder ze eerst in tekst om te zetten.

Bytes zijn getallen, geen teksttekens
ruw.py
from pathlib import Path

path = Path("pakket.bin")
path.write_bytes(bytes([0, 255, 10]))
data = path.read_bytes()
print(type(data).__name__, len(data))
print(data[1])
print(data.hex())
print(data[:2].hex())
Verwachte uitvoer
bytes 3
255
00ff0a
00ff

hex() toont iedere byte als twee hexadecimale cijfers. Dat is handig om exact te vergelijken zonder onleesbare tekens naar de console te sturen. De string 00ff0a in de uitvoer is dus een leesbare beschrijving van drie bytes, geen bestand van zes letters.

Binaire bestandsmodi

Open met rb om bytes te lezen en met wb om bytes te schrijven. Daarbij geef je geen encoding mee: die parameter hoort bij de vertaalslag tussen tekst en bytes. Een with-context sluit ook een binaire stream op de bekende manier.

Voor kleine bestanden zijn Path.read_bytes() en Path.write_bytes(data) korte vormen. Schrijven vervangt de bestaande inhoud en kan het bestand dus ook korter of leeg maken. De leeswaarde is bytes; de schrijfoperatie zelf levert een aantal geschreven bytes, terwijl onze hulpfunctie volgens afspraak None teruggeeft.

Lezen tot een afgesproken bytegrens

stream.read(size) leest hoogstens zoveel bytes. Als het bestand kleiner is, krijg je de beschikbare inhoud; bij nul krijg je lege bytes. Dat aantal is een grens in bytes, niet in letters.

Een bytegrens kan midden in een letter vallen
letters.py
from pathlib import Path

path = Path("naam.bin")
path.write_bytes("café".encode("utf-8"))
data = path.read_bytes()
print(len(data), len(data.decode("utf-8")))
try:
    data[:4].decode("utf-8")
except UnicodeDecodeError:
    print("vier bytes vormen nog geen hele UTF8-tekst")
print(data.decode("utf-8"))
Verwachte uitvoer
5 4
vier bytes vormen nog geen hele UTF8-tekst
café

De é gebruikt in UTF8 meer dan één byte. Een willekeurige byteprefix kan daardoor midden in die letter stoppen. Pas decode("utf-8") toe wanneer de gegevens volgens hun formaat complete UTF8-tekst zijn; niet om een willekeurig binair bestand makkelijker te kopiëren.

Kopiëren bewaart de representatie

Voor de kleine oefenbestanden kun je bronbytes lezen en ze ongewijzigd naar een ander pad schrijven. De bron blijft daarbij intact en een bestaand doel wordt vervangen. Bij grote bestanden zou je opeenvolgende blokken lezen om niet alles tegelijk in geheugen te houden; dat bouwen we hier nog niet.

Veelgemaakte fout

Met read_text(errors="ignore") lijkt een lastige afbeelding opeens leesbaar, maar ongeldige tekstbytes worden dan weggegooid. Terugschrijven maakt dat verlies niet ongedaan. Kies de juiste representatie voordat je leest, in plaats van decoderingsfouten te verbergen.

De prefixopdracht beoordeelt de teruggegeven bytes, niet hoeveel bytes een implementatie vooraf heeft ingelezen. Alles lezen en daarna slicen is hier dus een geldige vorm; bij grote bestanden vraagt geheugenverbruik een aparte afspraak.

AAN DE SLAG 0/4
1

Ruwe inhoud lezen

Maak read_blob(path): geef de volledige inhoud als bytes terug, inclusief nulbytes en ongeldige UTF8. Verander het bronbestand niet.

2

Ruwe inhoud schrijven

Maak write_blob(path, data): vervang de inhoud door exact de meegegeven bytes, ook bij lege data. Geef None terug.

3

Een byteprefix lezen

Maak read_prefix(path, size): geef hoogstens de eerste size bytes terug. size is niet-negatief; nul levert lege bytes en een te groot aantal levert de hele inhoud. Laat de bron intact.

4

Exact kopiëren

Maak copy_blob(source, destination): kopieer de kleine volledige bron als bytes naar het doel. Vervang bestaande doelinhoud, behoud de bron en geef None terug.

De invoer is telkens bytes of een niet-negatieve maximale leesgrootte. De controle gebruikt onder meer nulbytes, ongeldige UTF8 en lege bestanden. Bewaar de bytevolgorde exact; voeg geen regeleinden of tekstcodering toe.

Onthouden

  • Bytes beschrijven ruwe waarden; een encoding geeft die eventueel tekstbetekenis.
  • Binaire modi vertalen geen tekst of regeleinden.
  • Een byteprefix hoeft geen complete tekst te zijn.

In de laatste les vervangen we een tekstbestand pas nadat de nieuwe versie klaarstaat.

binary_files.py
UitvoerPython
›
›_

Hier komt je resultaat.

Voer je code uit en ontdek
wat je hebt gemaakt.

0/4 stappen
EVEN MEEKIJKEN

Bekijk hoe het kan.

Je mag de oplossing bekijken. Deze poging telt dan niet mee als afgeronde les. Met Reset kun je daarna opnieuw zelf oefenen.