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

Regels lezen en op tijd sluiten

Na deze les kun je
  • Regels zonder afsluitende newline en met overige witruimte verzamelen
  • Een bestand pas bij generatoriteratie openen en bij uitputting of close sluiten

Een boodschappenbestand kan duizend regels bevatten, terwijl je alleen het eerste bruikbare item zoekt. Je kunt het hele bestand inlezen, maar je kunt ook regel voor regel werken en stoppen zodra je genoeg weet. Dan wordt het belangrijk wie het bestand openhoudt en wanneer het sluit.

Een bestand is ook een iterator

Een geopend tekstbestand levert bij een for-lus telkens één regel. Het regeleinde hoort normaal bij die tekst: "brood\n" bevat dus meer dan alleen het woord. De laatste regel kan zonder regeleinde eindigen en wordt toch gelezen.

Gebruik removesuffix("\n") wanneer je precies dat ene afsluitende teken wilt verwijderen. strip() verwijdert ook spaties en tabs aan beide kanten; bij ingesprongen tekst verandert dat de gegevens. We gebruiken hier bestanden met gewone LF-regeleinden en lezen UTF8-tekst.

Regels hebben hun eigen regeleinde
lijst.txt
  brood  

melk
toon.py
with open("lijst.txt", encoding="utf-8") as stream:
    for number, line in enumerate(stream, start=1):
        print(number, repr(line.removesuffix("\n")))
Verwachte uitvoer
1 '  brood  '
2 ''
3 'melk'

Het voorbeeld toont de lege middelste regel als een lege string. Het bestand eindigt niet met een regeleinde, maar melk ontbreekt daardoor niet. repr maakt de bewaarde spaties zichtbaar; print alleen laat dat verschil minder goed zien.

Nummers toevoegen zonder apart tellertje

enumerate(stream, start=1) levert een paar met een nummer en de volgende regel. Eén is hier een keuze voor de menselijke regelnummering; Python begint bij weglaten van start op nul. Een lijst kan de paren bewaren wanneer je ze na het sluiten nog nodig hebt.

Een lijst bevat alle resultaten meteen. Een generator met yield pauzeert na ieder resultaat en gaat pas verder bij het volgende verzoek. Je kende generators al; nu gebruiken we dezelfde pauze bij een echte bron die ook gesloten moet worden.

Pauzeren houdt het bestand open

Zet je de bestandscontext binnen de generator, dan opent hij bij de eerste iteratie. Terwijl hij bij yield wacht, blijft de context actief. Volledig doorlopen verlaat de context vanzelf; bij bewust vroeg stoppen gebruik je close() op deze generator.

Een generator vroeg sluiten
stops.txt
duin
bos
stop.py
def names(path):
    with open(path, encoding="utf-8") as stream:
        try:
            for line in stream:
                yield line.removesuffix("\n")
        finally:
            print("opruimen")

route = names("stops.txt")
print(next(route))
route.close()
print("klaar")
Verwachte uitvoer
duin
opruimen
klaar

De regel opruimen verschijnt door het expliciete sluiten, ook al is bos nooit opgevraagd. Een losse break bij de aanroeper is geen belofte dat een bewaarde generator direct sluit. Maak die levensduur daarom zichtbaar in je eigen code.

Veelgemaakte fout

Een generator buiten with teruggeven lijkt handig, maar die context sluit al bij het terugkeren uit de functie. De eerste poging om later uit de stream te lezen geeft dan een fout over een gesloten bestand. Laat bij yield de lus én de context binnen de generator staan.

Bij zoeken mag je text.strip() gebruiken om te bepalen of een regel alleen witruimte bevat. Geef daarna de oorspronkelijke tekst zonder regeleinde terug; anders gooi je geldige spaties van de gevonden regel alsnog weg. Een return binnen with sluit de stream voordat het resultaat bij de aanroeper komt.

Vraag bij ieder volgend resultaat alleen de benodigde volgende logische regel op. Eerst list(stream) maken leest alles vooruit, ook als je daarna yield gebruikt. We meten die regelverzoeken, niet de interne bytebuffer van een tekststream.

Geeft een latere bronregel een fout, dan verlaat ook de generator zijn with-context. De stream sluit en de oorspronkelijke fout bereikt de aanroeper. Dezelfde opruiming blijft nodig bij bewust vroeg sluiten.

AAN DE SLAG 0/4
1

Regels bewaren

Maak read_lines(path, *, opener=open): geef een lijst met alle regels zonder hun afsluitende newline. Bewaar lege regels en overige witruimte; sluit de stream.

2

Vanaf één nummeren

Maak numbered_lines met dezelfde opener: geef een lijst van (regelnummer, tekst)-tuples vanaf 1. Bewaar lege regels, spaties en de laatste regel zonder newline.

3

Lui lezen en sluiten

Maak iter_lines als generator: open pas bij de eerste iteratie, yield regels zonder newline en houd de stream open tijdens een pauze. Volledige consumptie of close() sluit hem. Vraag latere logische regels pas bij een volgende iteratie op. Sluit ook wanneer het opvragen van de volgende bronregel een fout geeft en laat dezelfde fout door.

4

Eerste bruikbare regel

Maak first_nonempty: zoek de eerste regel waarvan strip() niet leeg is. Geef diens oorspronkelijke tekst zonder newline terug, of None. Sluit de stream ook bij vroeg return.

De vier functies gebruiken de meegegeven opener, zodat de controle de echte streams kan volgen. Bestanden openen met r en encoding="utf-8"; de regels blijven ongewijzigd behalve hun afsluitende \n. Verander het invoerbestand niet.

Onthouden

  • Itereren leest regels inclusief hun eventuele regeleinde.
  • Een lijst is direct klaar; een generator houdt werk en middelen vast tijdens een pauze.
  • Een context sluit bij verlaten; een vroeg gestopte generator sluit je bewust.

Hierna lezen we tabellen waarin een komma soms bij de inhoud van één veld hoort.

line_tools.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.