7.1 Filinnlesing linje for linje
Lese en tekstfil linje for linje, fjerne linjeskift, splitte på felt og konvertere til tall — nesten alltid steget som fyller klassemodellen med data.
som en deloppgave inne i Oppgave 4, den store klassemodelleringen. Bestillingen er den samme hvert år: «les fila og opprett objektene».
Sjangeren er F — stor klassemodellering, altså å bygge samarbeidende klasser fra en beskrevet oppgave. Fil-deloppgaven er typisk verdt 5–10 poeng.
Dette må sitte — bokas høyeste prioritetsnivå. Mønsteret er helt fast, og du
kan lære det utenat: åpne fila, les én linje, løkk til den tomme strengen, rens linja, del den i felt, konverter tallene, lag objektet, les neste linje, lukk fila.
To feil tar nesten alle poengene som mistes her. #8 — glemt .rstrip() eller feil skilletegn i .split() — og #4 — glemt int(...), slik at et tall fortsatt er tekst. Begge er stumme: programmet krasjer ikke, det svarer bare feil.
Kapitlet er langt (~55 min) og er delt i fire løkker med tidsanslag.
Forkunnskaper — sist du var her
Kapitlet bygger på kap. 3.3 — Strenger og
kap. 3.1 — Lister, og bruker klassene fra
kap. 5.1 når fila skal fylle en modell.
De tre tingene som avgjør alt her, friskes opp med kjørt kode.
1. .split() deler en tekst i en liste av felt:
linje = "skrue;120;4" felt = linje.split(";")
print(felt)
print(len(felt), felt[0], felt[2])Utskrift:
['skrue', '120', '4']
3 skrue 42. .rstrip() fjerner blanktegn i enden — også linjeskiftet:
raa = "Ada 24\n" print(len(raa))
ren = raa.rstrip()
print(len(ren))Utskrift:
7
6Legg merke til at lengden går fra 7 til 6: linjeskiftet er ett tegn, og det er det eneste som forsvant. Mellomrommet inni teksten ble ikke rørt, fordi.rstrip() bare tar fra høyre.
3. Felt fra en fil er alltid tekst. Skal du regne, må de gjennom int(...):
felt = "120" print(felt + felt)
print(int(felt) + int(felt))Utskrift:
120120
240Har du de tre, er resten mønsteret.
Løkke 1 — Å åpne en fil og lese én linje (~14 min)
Et treningssenter har medlemslista si i en tekstfil. Den ser slik ut:
Innholdet i medlemmer.txt:
Ada 24
Nora 31
Per 19Tre linjer, hver med et navn, et mellomrom og en alder. Programmet skal lese den inn og gjøre noe med den — og hele Del 7 handler om nettopp den jobben.
En fil leses oppfra og ned, én linje om gangen. Du kan ikke hoppe rundt i den, og du kan ikke lese den samme linja to ganger uten å åpne fila på nytt.
Åpner en tekstfil for lesing og gir deg et filobjekt som holder styr på hvor langt du har lest:
fil = open("medlemmer.txt")
linje = fil.readline()
print(linje)
fil.close()Utskrift:
Ada 24
Legg merke til den tomme linjen i utskriften. Den kommer av at readline tar med linjeskiftet på slutten av linja, og print legger til ett til. Det er den første grunnen til at .rstrip() finnes.
Filnavnet er en helt vanlig tekst, og du kan bygge det med +:serienavn + ".txt". Finnes ikke fila, stopper programmet, og siste linje i feilmeldingen er FileNotFoundError med filnavnet i.
Leser neste linje og gir den som en tekst, med linjeskiftet på slutten.
Filobjektet husker hvor du er. Første kall gir linje 1, andre kall gir linje 2, og så videre — du trenger ingen teller.
Er det ingen flere linjer igjen, gir readline den tomme teksten "". Det er signalet om at fila er slutt, og det er nettopp den verdien løkka bruker som stoppbetingelse.
En blank linje midt i fila gir "\n", ikke "". De to er ikke det samme, og det er derfor løkka ikke stopper for tidlig på en tom linje.
Bokas trettende kodemønster, og det du skal kunne skrive uten å tenke:
fil = open(filnavn)
linje = fil.readline()
while linje != "":
...gjor noe med linje...
linje = fil.readline()
fil.close()Blokken er en mal med plassholdere og er merket som ikke-kjørbar.
Fem faste deler, og hver av dem er verdt poeng:
1. open før løkka.
2. Første readline før løkka — ellers har while ingenting å teste.
3. while linje != "": — løkk til den tomme teksten.
4. linje = fil.readline() NEDERST i løkka. Glemmer du den, leses den samme
linja for alltid, og programmet henger.
5. fil.close() etter løkka.
Legg merke til at readline står to steder. Det er det som ser rart ut første gang, og det er den vanligste kilden til en løkke som aldri stopper.
Skriv et program som leser medlemmer.txt linje for linje og skriver ut hver linje, uten den ekstra tomme linjen mellom.
Malen satt rett inn, med .rstrip() på hver linje:
Innholdet i medlemmer.txt:
Ada 24
Nora 31
Per 19fil = open("medlemmer.txt")
linje = fil.readline()
antall = 0 while linje != "":
print(linje.rstrip())
antall = antall + 1 linje = fil.readline()
fil.close()
print("Antall linjer:", antall)Utskrift:
Ada 24
Nora 31
Per 19
Antall linjer: 3Telleren er lagt inn med vilje. Den viser at filinnlesing-løkka er en helt
vanlig while-løkke: alt du kan om tellere og akkumulatorer fra
kap. 1.3, virker uendret.
Slik ville sensor sett på det: fem ting gir uttelling hver for seg — open,
første readline, while linje != "":, kroppen, og readline nederst.
Glemmer du fil.close(), er trekket lite; glemmer du den nederste readline, henger programmet, og det koster mer.
Prøv å fjerne .rstrip() i hodet: da ville hver utskriftslinje fått en tom
linje under seg, fordi linjeskiftet fra fila kommer i tillegg til det print legger på.
(Innstegsoppgave — gjengivelse.) Under står filinnlesing-løkka med fire linjer stokket om.
a) Sett linjene i riktig rekkefølge: fil.close(), while linje != "":,fil = open("data.txt"), linje = fil.readline().
b) Hvilken av linjene må stå to ganger, og hvor?
c) Hva skjer hvis du glemmer den siste av de to?
Løkke 2 — Rens først, del så (~14 min)
En linje fra en fil er sjelden bare tekst du vil skrive ut. Som regel er den et
datasett med flere felt, og du vil ha tak i hvert av dem.
Rekkefølgen på de to stegene er fast og verdt å tatovere: .rstrip() FØR.split(). Motsatt rekkefølge gir en stum feil som dukker opp langt unna.
Malen for å hente feltene ut av en linje. Rens først, del så:
linje = "skrue;120;4\n" felt = linje.rstrip().split(";")
print(felt)
print(felt[0], felt[2])Utskrift:
['skrue', '120', '4']
skrue 4De to metodene leses fra venstre: .rstrip() gir en ny tekst uten linjeskiftet, og .split(";") deler den i en liste.
Uten argument deler .split() på blanktegn, som passer når feltene er skilt med mellomrom: "Ada 24".split() gir ['Ada', '24'].
Skilletegnet er en del av dataformatet, ikke noe du velger. Bruk det tegnet
fila faktisk bruker.
Feilen er stum: programmet krasjer ikke, det svarer bare feil.
linje = "skrue;120;4\n" uten_rens = linje.split(";")
med_rens = linje.rstrip().split(";")
print(uten_rens)
print(med_rens)
print(uten_rens[2] == "4", med_rens[2] == "4")Utskrift:
['skrue', '120', '4\n']
['skrue', '120', '4']
False TrueSe på det siste feltet. Uten rensing henger linjeskiftet igjen i det, og
sammenligningen med "4" svarer False — selv om det «ser riktig ut» når du skriver det ut.
Det rammer bare det siste feltet, og det gjør feilen ekstra lumsk: alt annet i programmet virker.
Feil skilletegn er den andre halvdelen av #8. Skriver du .split(",") på en
linje som er skilt med semikolon, får du én stor bit i stedet for tre, ogfelt[1] stopper programmet med IndexError: list index out of range.
Motgiften: skriv alltid linje.rstrip().split(sep) som én lenke, i den
rekkefølgen. Og skriv ut felt én gang mens du utvikler — da ser du med én gang om delingen ble som ventet.
Les varer.txt, der hver linje har formen varenavn;antall;pris, og skriv ut navnet og den samlede verdien for hver vare.
varer.txt:skrue;120;4 mutter;80;2 bolt;45;7Løkka er den samme som før; det nye ligger i de tre linjene inne i den:
fil = open("varer.txt")
linje = fil.readline()
total = 0 while linje != "":
felt = linje.rstrip().split(";")
navn = felt[0]
antall = int(felt[1])
pris = int(felt[2])
verdi = antall * pris print(navn, verdi)
total = total + verdi linje = fil.readline()
fil.close()
print("Samlet verdi:", total)Utskrift:
skrue 480 mutter 160 bolt 315
Samlet verdi: 955De tre navngitte variablene er et bevisst valg. Du kunne skrevetint(felt[1]) * int(felt[2]) direkte, men da må leseren — og du selv, om en time — telle indekser for å skjønne hva som er hva. Én linje per felt gjør besvarelsen lettere å rette.
Slik ville sensor sett på det: her ligger poengene på løkkestrukturen (fem
deler), på .rstrip().split(";") i riktig rekkefølge, på int(...) rundt begge tallene, og på akkumulatoren utenfor løkka. Hadde du glemt int på prisen, villeantall * pris gitt en lang tekst i stedet for et tall — og feilen ville dukket opp i utskriften, ikke som en feilmelding.
Merk at felt[0] ikke trenger int. Navnet skal være tekst. Konverter bare
det du faktisk skal regne med.
En linje fra en fil ser slik ut:
linje = "mandag,3\n"a) Skriv linjen som deler den i felt på riktig måte.
b) Hva er felt[1] etter delingen, og hvilken type har den?
c) Skriv linjen som gir deg antallet som et tall.
Hva skriver programmet ut? Fila turer.txt inneholder:
mandag,3 tirsdag,7 onsdag,0 torsdag,5fil = open("turer.txt")
linje = fil.readline()
dager = []
total = 0 while linje != "":
felt = linje.rstrip().split(",")
if int(felt[1]) > 2:
dager.append(felt[0])
total = total + int(felt[1])
linje = fil.readline()
fil.close()
print(dager)
print(len(dager), total)Løkke 3 — Fila fyller modellen (~14 min)
Nå kommer selve eksamensoppgaven. Fila inneholder sjelden bare tall som skal summeres — den inneholder objektene programmet skal jobbe med.
Bestillingen i settene er formulert nesten likt hvert år: «les fila og opprett objektene». Mønsteret er filinnlesing-løkka med to nye linjer inne i den: lag objektet, legg det i samlingen.
Bokas fjortende kodemønster, og den faste fil-deloppgaven i Oppgave 4:
def les_alle(filnavn):
samling = []
fil = open(filnavn)
linje = fil.readline()
while linje != "":
felt = linje.rstrip().split(sep)
obj = Klasse(felt[0], int(felt[1]))
samling.append(obj)
linje = fil.readline()
fil.close()
return samlingBlokken er en mal med plassholdernavn og er merket som ikke-kjørbar.
Tre ting i tillegg til filinnlesing-løkka:
1. Samlingen opprettes FØR løkka — [] eller {}. Inne i løkka ville den
blitt nullstilt hver runde.
2. Objektet lages av feltene, med int(...) på det som skal være tall.
3. return ETTER løkka, ikke inne i den. Returnerer du inne, får du bare
det første elementet — det er feilkode #7, for tidlig retur.
Skal samlingen være en ordbok, byttes samling.append(obj) medsamling[felt[0]] = obj, og alt annet står.
Fila medlemmer.txt har linjer på formen navn alder. Skriv les_medlemmer(filnavn) som returnerer en liste av Medlem-objekter, med alderen som heltall.
medlemmer.txt:Ada 24
Nora 31
Per 19Klassen først, så innlesingsfunksjonen, så bruken:
class Medlem:
def __init__(self, navn, alder):
self._navn = navn self._alder = alder
def hent_alder(self):
return self._alder
def __str__(self):
return self._navn + " (" + str(self._alder) + ")"
def les_medlemmer(filnavn):
medlemmer = []
fil = open(filnavn)
linje = fil.readline()
while linje != "":
felt = linje.rstrip().split()
m = Medlem(felt[0], int(felt[1]))
medlemmer.append(m)
linje = fil.readline()
fil.close()
return medlemmer
alle = les_medlemmer("medlemmer.txt")
for m in alle:
print(m)
print("Antall:", len(alle))
sum_alder = 0 for m in alle:
sum_alder = sum_alder + m.hent_alder()
print("Sum alder:", sum_alder)Utskrift:
Ada (24)
Nora (31)
Per (19)
Antall: 3
Sum alder: 74Legg merke til .split() uten argument. Feltene i denne fila er skilt med
mellomrom, og den formen deler på blanktegn av seg selv.
Summeringen nederst beviser at konverteringen virket. Hadde int(...) vært
glemt, ville sum_alder = sum_alder + m.hent_alder() stoppet programmet medTypeError: unsupported operand type(s) for +: 'int' and 'str' — og det er faktisk en av de snille formene feilkode #4 tar, fordi den i det minste sier fra.
Slik ville sensor sett på det: dette er en typisk 8–10-poengs deloppgave.
Poengene ligger på lista før løkka, filinnlesing-løkkas fem deler,.rstrip().split(), int(...) på alderen, opprettelsen av objektet,append, og return etter løkka. Hver av dem teller for seg.
— naturlig pausepunkt —
Fila containere.txt på en resirkuleringsstasjon har linjer på formen materiale;fylling, der fyllingen er et prosenttall.
a) Skriv klassen Container med konstruktør, en aksessor for fyllingen, og
en __str__ på formen glass: 40 %.
b) Skriv les_containere(filnavn) som returnerer en liste avContainer-objekter.
c) Skriv kode som leser fila, skriver ut alle containerne, og teller hvor
mange som har fylling på minst 80.
Hva skriver programmet ut? Filavarer.txt inneholder:
skrue;120;4 mutter;80;2 bolt;45;7fil = open("varer.txt")
linje = fil.readline()
navn = []
while linje != "":
felt = linje.split(";")
navn.append(felt[0])
linje = fil.readline()
fil.close()
print(navn)
print(len(navn))Se nøye på delingen.
Løkke 4 — Header, filnavn og den korte løkkeformen (~13 min)
Tre praktiske detaljer til slutt. Alle tre har dukket opp i settene, og alle tre er raske å lære.
Noen filer har en overskriftslinje øverst som ikke er data. Løsningen er ett ekstra readline-kall før løkka:
fil = open("sykler.txt")
header = fil.readline()
print("Header:", header.rstrip())
linje = fil.readline()
antall = 0 while linje != "":
antall = antall + 1 linje = fil.readline()
fil.close()
print("Datalinjer:", antall)Utskrift:
Header: kode stativ turer
Datalinjer: 3Det ene kallet «bruker opp» første linje, og løkka starter på linje 2. Du trenger ikke å ta vare på headeren; fil.readline() alene på en linje er nok.
Sjekk alltid om fila har en header før du koder. Har den det og du glemmer å
hoppe over den, prøver programmet å konvertere ordet turer til et tall, og stopper med ValueError: invalid literal for int() with base 10: 'turer'.
Filnavnet er en helt vanlig tekst og kan settes sammen med +:
navn = "Kolonier" filnavn = navn + ".txt" fil = open(filnavn)
linje = fil.readline()
antall = 0 while linje != "":
antall = antall + 1 linje = fil.readline()
fil.close()
print(filnavn, "har", antall, "spillere")Utskrift:
Kolonier.txt har 3 spillereFormen navn + ".txt" er en gjenganger i settene: hvert objekt har sin egen fil, og navnet på objektet gir navnet på fila.
Punktumet og filendelsen må skrives inn selv — navn + "txt" gir Koloniertxt, og programmet stopper med FileNotFoundError.
En kortere form som gjør det samme som filinnlesing-løkka:
fil = open("medlemmer.txt")
for linje in fil:
print(linje.rstrip())
fil.close()Utskrift:
Ada 24
Nora 31
Per 19Den er kortere og har ingen risiko for en uendelig løkke, siden det ikke finnes noen readline du kan glemme.
Boka bruker likevel while-formen som standard, av to grunner. Fagets egne
løsningsforslag skriver den, og den lar deg gjøre ting midt i lesingen — som å hoppe over en header eller å lese to linjer om gangen — som den korte formen ikke gjør like enkelt.
Kan du begge, er du trygg uansett hvilken form oppgaveteksten viser.
Lukker fila og frigjør den. Står etter løkka, aldri inne i den.
Lukker du fila inne i løkka, stopper programmet på neste readline, og siste linje i feilmeldingen er ValueError: I/O operation on closed file..
Glemmer du close helt, virker programmet som regel likevel — men det er en liten trekkfeil, og det er en dårlig vane i større programmer. Skriv den med én gang du har skrevet open, så glemmer du den ikke.
Et treningssenter har en fil okter.txt med linjer på formendag;aktivitet;antall. Skriv en klasse Okt og en funksjon som leser fila og returnerer en ordbok fra aktivitetsnavn til samlet antall deltakere. Fila har ingen header.
okter.txt:mandag;spinning;14 mandag;yoga;9 tirsdag;spinning;20 onsdag;yoga;6Legg merke til at samme aktivitet står på flere linjer. Da er teller-idiomet fra
kap. 3.2 svaret — nå med data fra en fil:
class Okt:
def __init__(self, dag, aktivitet, antall):
self._dag = dag self._aktivitet = aktivitet self._antall = antall
def hent_aktivitet(self):
return self._aktivitet
def hent_antall(self):
return self._antall
def __str__(self):
return self._dag + ": " + self._aktivitet + " (" + str(self._antall) + ")"
def les_okter(filnavn):
okter = []
fil = open(filnavn)
linje = fil.readline()
while linje != "":
felt = linje.rstrip().split(";")
okter.append(Okt(felt[0], felt[1], int(felt[2])))
linje = fil.readline()
fil.close()
return okter
def deltakere_per_aktivitet(okter):
d = {}
for o in okter:
a = o.hent_aktivitet()
if a not in d:
d[a] = 0 d[a] = d[a] + o.hent_antall()
return d
alle = les_okter("okter.txt")
for o in alle:
print(o)
print(deltakere_per_aktivitet(alle))Utskrift:
mandag: spinning (14)
mandag: yoga (9)
tirsdag: spinning (20)
onsdag: yoga (6)
{'spinning': 34, 'yoga': 15}Jobben er delt i to funksjoner med vilje. Den ene leser fila, den andre
regner. Det er slik oppgaveteksten som regel deler den også — «c) les fila» og
«d) skriv en metode som …» — og det gjør at du kan få full uttelling på den ene selv om du står fast på den andre.
Teller-idiomet er uendret fra kap. 3.2:if a not in d: og d[a] = 0 før du legger til. Uten den sjekken stopper programmet med KeyError på den første aktiviteten — det er feilkode #14.
Legg merke til rekkefølgen i ordboka. spinning kommer først fordi den ble
opprettet først, ikke fordi den er størst. Ordbøker husker innsettingsrekkefølgen.
Slik ville sensor sett på det: dette er en 12–15-poengs deloppgave i den
store oppgaven. Poengene ligger på klassen, filinnlesing-løkka, int(...) på antallet, ordboka opprettet før løkka, if a not in d:-sjekken, akkumuleringen, og return etter løkka. Har du glemt sjekken, står resten — men programmet stopper, og det koster mer enn en stum feil.
Fila sykler.txt har en overskriftslinje øverst, og deretter linjer på formen kode stativ turer:
kode stativ turer
R31 Blindern 12
R48 Majorstuen 5
R07 Toyen 23a) Skriv les_sykler(filnavn) som returnerer en ordbok fra sykkelkode
til antall turer, og som hopper over headeren.
b) Bruk funksjonen og skriv ut ordboka og det samlede antallet turer.
c) Hva ville skjedd om du glemte å hoppe over headeren?
En brettspillklubb har én fil per spill. Fila heter <spilltittel>.txt og inneholder ett navn per linje — de som eier spillet.
Filene Kolonier.txt og Ordjakt.txt inneholder:
Ada
Nora
PerAda
Pera) Skriv klassen Spill med konstruktør som tar tittelen, en tom liste av
eiere, og metoden les_eiere(self) som bygger filnavnet av tittelen og fyller lista.
b) Legg til antall_eiere(self) og en __str__ på formenKolonier (3 eiere).
c) Lag de to spillene, les inn eierne, og skriv ut begge.
En medstudent har skrevet denne innlesingsfunksjonen:
def les_varer(filnavn):
fil = open(filnavn)
linje = fil.readline()
while linje != "":
varer = []
felt = linje.split(";")
varer.append(felt[0])
return varer fil.close()Fila varer.txt inneholder:
skrue;120;4 mutter;80;2 bolt;45;7a) Funksjonen har tre feil. Hvilke?
b) Gi et konkret moteksempel: hva gir kalletles_varer("varer.txt"), og hva skulle det gitt?
c) Skriv den rettede funksjonen.
Fem feil koster nesten alle poengene som mistes på filbehandling:
- Glemt linje = fil.readline() nederst i løkka. Programmet henger og
skriver den samme linja for alltid. Den mest synlige feilen, og den letteste å rette.
- #8 — glemt .rstrip(). Linjeskiftet henger igjen i det siste feltet, og
sammenligninger som ser riktige ut, svarer False. Stum feil.
- #8 — feil skilletegn. .split(",") på en semikolonfil gir én stor bit, og
felt[1] stopper programmet med IndexError.
- #4 — glemt int(...). Feltene er alltid tekster. Symptomet er en sum som
blir en lang tekst, eller en TypeError ved regning.
- #7 — return inne i løkka. Funksjonen gir bare det første elementet.
Returner etter løkka.
Og to plasseringsfeil i samme familie: samlingen opprettet inne i løkka (den nullstilles hver runde), og fil.close() inne i løkka (neste readline stopper programmet).
Begrepsbank
Begrepsbanken er flashcard- og repetisjonsstoff — den gjentar det du nettopp har lest. Hopp trygt over ved førstegangslesing; tidsanslaget for kapitlet gjelder kjernestoffet.
En fil som inneholder tegn, organisert i linjer skilt av linjeskift.
Den kan åpnes i en hvilken som helst editor, og det er nettopp derfor eksamensoppgaver bruker den: du kan se hva som står der, og du kan lage din egen testfil på ti sekunder.
Filer med bilder eller lyd er ikke tekstfiler og leses helt annerledes. De er ikke pensum her.
Det open(...) gir deg. Det er ikke innholdet i fila, men en forbindelse til den, med en usynlig markør for hvor langt du har lest.
Markøren er grunnen til at readline gir en ny linje hver gang uten at du trenger en teller.
Skal du lese den samme fila to ganger, må du åpne den på nytt. Markøren kan ikke spoles tilbake med de konstruksjonene som er pensum her.
Tegnet som avslutter en linje. I Python skrives det \n inne i en tekst, og det er ett tegn, ikke to.
Hver linje readline gir deg, har det på slutten — bortsett fra den aller siste linja i fila, hvis fila ikke slutter med en tom linje.
Du ser det aldri i en utskrift. Det viser seg bare som at teksten fortsetter på neste linje, eller som en ekstra tom linje når du skriver ut en urenset linje.
readline gir "" når det ikke er flere linjer igjen. Det er signalet løkka bruker.En blank linje midt i fila gir "\n", som ikke er den tomme teksten. Løkka stopper derfor ikke der, og det er riktig — en blank linje er data, ikke slutt.
Skriver du while linje.rstrip() != "": i stedet, stopper løkka på den første blanke linja. Det er sjelden det du vil.
Bokas faste mønster, i fem deler: open, første readline,while linje != "":, kroppen, readline nederst — og close etter.
At readline står to steder, er det som ser rart ut første gang. Grunnen er atwhile tester før hver runde, så den trenger en verdi allerede første gang.
Glemmer du den nederste, henger programmet. Det er den mest synlige feilen i hele kapitlet, og den letteste å rette.
En av bitene du får når du deler en linje: i "skrue;120;4" er "skrue","120" og "4" de tre feltene.
Feltene er alltid tekster, også når de ser ut som tall. Skal du regne med et
felt, må det gjennom int(...) — det er feilkode #4.
Rekkefølgen er fast og bestemt av dataformatet, så du henter dem med indeks.
Skriv gjerne én navngitt variabel per felt; det gjør besvarelsen lettere å rette.
Tegnet feltene er skilt med: semikolon, komma, kolon eller mellomrom.
Det er en del av dataformatet, ikke noe du velger. Bruker fila semikolon og du skriver .split(","), får du én stor bit i stedet for flere små — og feilen dukker opp langt unna, som en IndexError.
Er formatet uklart i en eksamensoppgave, skriv en kommentar om hvilket skilletegn du har forutsatt. Det er den ene legitime bruken av kommentarer i en besvarelse.
En overskriftslinje øverst i fila som ikke er data.
Hoppes over med ett ekstra fil.readline() før løkka. Du trenger ikke å ta vare på returverdien.
Glemmer du det, prøver programmet å konvertere et ord fra overskriften til et tall, og stopper med ValueError — med selve ordet i feilmeldingen. Ser du et overskriftsord der, vet du med én gang hva som mangler.
navn + ".txt" — filnavnet er en helt vanlig tekst og kan settes sammen.Formen er en gjenganger i settene: hvert objekt har sin egen fil, og navnet på objektet gir navnet på fila.
Punktumet må skrives inn selv. Og skal navnet komme fra en instansvariabel, skriver du self._tittel + ".txt" inne i metoden.
Mønsteret bak den faste fil-deloppgaven: lista opprettes før løkka, objektet lages av feltene inne i den, og lista returneres etter.
Tre plasseringer avgjør om det virker: samlingen over løkka, append
inne i den, return etter den.
Skal samlingen være en ordbok, byttes append med d[felt[0]] = obj, og alt annet står.
Varianten der du skal kunne slå opp på et navn eller en kode senere:d[felt[0]] = Klasse(...).
Nøkkelen er som regel det første feltet, og verdien er hele objektet — ikke rådataen. Da beholder du tilgangen til objektets metoder.
En ordbok kan aldri ha to like nøkler. Har fila to linjer med samme navn,
overskrives den første uten en advarsel. Er det et problem, må oppgaven si
hva som skal skje.
Glemt int(...) eller float(...) på et felt.
Symptomene er to. Enten en TypeError når du prøver å regne med teksten — den snille varianten, som sier fra. Eller en sammenligning som svarer feil:"9" > "10" er sann, fordi tekster sammenlignes tegn for tegn.
Den siste er stum og farlig. Konverter så snart feltet er hentet ut, ikke der du skal bruke det.
Glemt .rstrip() eller feil skilletegn.
Den rammer bare det siste feltet når det gjelder .rstrip(), og det gjør den ekstra lumsk: alt annet i programmet virker, og feilen dukker opp den dagen noen sammenligner det siste feltet med noe.
Motgiften er å skrive linje.rstrip().split(sep) som én fast lenke, hver gang, uten å vurdere om du «trenger» rensingen.
.rstrip() fjerner blanktegn bare til høyre; .strip() fjerner i begge ender.For en linje fra en fil er .rstrip() det riktige: det er linjeskiftet på slutten du vil bort med, og et mellomrom i starten kan være meningsfullt.
For et felt som kan ha fått et mellomrom rundt seg — for eksempel hvis fila er skrevet med "skrue ; 120" — er .strip() på hvert felt nyttig. Da renser du først linja, deler, og renser hvert felt.
Én funksjon som leser fila, én som regner på resultatet.
Det er slik oppgaveteksten som regel deler den også: «c) les fila og opprett objektene» og «d) skriv en metode som finner …».
Gevinsten på eksamen er konkret: du kan få full uttelling på den ene selv om du står fast på den andre. Og du kan bruke en metode oppgaven beskriver, uten selv å ha skrevet den.
Feilmeldingen når fila ikke finnes. Siste linje inneholder filnavnet, slik du skrev det.
De vanligste årsakene er en skrivefeil i navnet, en glemt .txt, eller at fila ligger i en annen mappe enn programmet.
På eksamen får du filnavnet oppgitt i teksten. Skriv det av tegn for tegn — også store og små bokstaver, som teller.
Den ene legitime bruken av kommentarer i en besvarelse: å dokumentere en antakelse når oppgaveteksten er underspesifisert.
I filoppgaver er det særlig tre ting: hvilket skilletegn du har forutsatt, om fila har en header, og hva du gjør med en linje som mangler et felt.
Én linje er nok: # forutsetter at filen ikke har overskriftslinje. Det viser at valget er bevisst, ikke tilfeldig.
Filobjektet husker hvor du er, så når løkka er ferdig, er fila «brukt opp».
Trenger du å gå gjennom dataene på nytt, er svaret nesten alltid å lagre dem i en liste eller ordbok ved første gjennomlesing, og så løpe gjennom samlingen så mange ganger du vil.
Det er også grunnen til at fil-deloppgaven kommer først i Oppgave 4: alle de senere deloppgavene jobber på samlingen, ikke på fila.
Formen der innlesingen er en metode på klassen, ikke en frittstående funksjon: def les_eiere(self): som fyller self._eiere.
Filnavnet bygges da av objektets egen instansvariabel:open(self._tittel + ".txt").
Metoden returnerer som regel ingenting — den endrer objektet, og resultatet hentes med en aksessor etterpå. Det er en helt vanlig og riktig form i den store oppgaven.
for linje in fil: er kortere og kan ikke henge. Boka bruker likevelwhile-formen.To grunner: fagets egne løsningsforslag skriver den, og den lar deg gjøre ting midt i lesingen — hoppe over en header, lese to linjer om gangen, eller stoppe på en bestemt verdi.
Kan du begge, er du trygg uansett hvilken form oppgaveteksten viser. Og du kan alltid velge den korte når oppgaven bare ber om «gå gjennom alle linjene».
Ordboka husker den rekkefølgen nøklene ble opprettet i, ikke når de sist ble endret, og ikke etter størrelse.
Leser du fila ovenfra og ned, får ordboka nøklene i filas rekkefølge. Det er verdt å vite når du skal spore en utskrift av hele ordboka.
Skal noe sorteres, må det gjøres eksplisitt — og bokas stil er en eksplisitt finn-beste-løkke, ikke en ferdig sorteringsfunksjon.
Den faste plassen filbehandling har på eksamen: én deloppgave inne i den store modelleringen, verdt 5–10 poeng.
Bestillingen er nesten alltid «les fila og opprett objektene», og formatet oppgis i oppgaveteksten med et eksempel på et par linjer.
Skriv av eksempellinjen på kladdepapiret og marker skilletegnet og hvilket felt som er hva, før du koder. To minutter der sparer deg for begge halvdelene av feilkode #8.
Dette kapitlet er skrevet av Anthropics toppmodeller (Claude Opus og Claude Fable) og er foreløpig ikke manuelt gjennomgått — kvalitetskontrollen gjøres av uavhengige KI-agenter, og innmeldte feil rettes fortløpende. Funnet en feil? Meld fra, så retter vi den. Les mer om hvordan innholdet lages.
Skolesaga er en uavhengig læringsressurs og er ikke tilknyttet eller godkjent av Universitetet i Oslo. Dette er ikke offisielt studiemateriell. Les mer.