Osa 6

Läsa filer

När man programmerar kan man ibland behöva hantera data som finns lagrad i filer. Datorprogram kan läsa data från filer och skriva data till filer. Också stora mängder data i filer kan enkelt behandlas automatiskt.

Under den här kursen kommer vi endast att arbeta med textfiler. De här filerna består av rader med text. Till exempel är kodeditorn Visual Studio Code kompatibel med textfiler. Obs! Även om ordbehandlingsprogram som Microsoft Word ofta används med filer som innehåller text, är Word-dokument inte textfiler. Dokumenten innehåller också annan information om till exempel textformat, vilket gör det mer komplicerat att behandla filerna i Python-program.

Att läsa data från en fil

Vi börjar arbeta med textfilen exempel.txt som innehållet följande:

Exempeldata

Hej alla! Vår exempelfil består av tre rader. Det här är den sista raden.

Ett enkelt sätt att använda filer i Python är med with-satsen. Den inledande raden öppnar filen följt av blocket där vi kan komma åt filen. Efter blocket stängs filen automatiskt och då kan den inte mera användas i programmet innan man öppnar den igen.

Den här koden öppnar alltså filen, läser dess innehåll och skriver ut det. Därefter stängs filen:

with open("exempel.txt") as fil:
    innehall = fil.read()
    print(innehall)
Exempelutskrift

Hej alla! Vår exempelfil består av tre rader. Det här är den sista raden.

Variabeln fil är en så kallad file handle ("filhandtag"). Via variabeln kan vi komma åt filen så länge den är öppen. Här använde vi metoden read som returnerar filens innehåll som en enda sträng. I det här fallet skulle strängen se ut så här:

"Hej alla!\nVår exempelfil består av tre rader.\nDet här är den sista raden."

\n motsvarar radbrytningarna i texten.

Gå igenom innehållet i en fil

Metoden read fungerar bra för att skriva ut hela innehållet i en fil, men ofta vill vi gå igenom innehållet rad för rad.

Man kan tänka att en textfil är en lista av strängar, där varje sträng finns på sin egen rad i filen. Vi kan som vanligt gå igenom denna lista med en for-loop.

Följande exempel läser in vår exempelfil med hjälp av en for-loop, tar bort radbrytningarna, räknar antalet rader och skriver ut varje rad tillsammans med sitt radnummer. Programmet håller också koll på radernas längder:

with open("exempel.txt") as fil:
    raknare = 0
    totallangd = 0

    for rad in fil:
        rad = rad.replace("\n", "")
        raknare += 1
        print("Rad", raknare, rad)
        langd = len(rad)
        totallangd += langd

print("Radernas totallängd:", totallangd)
Exempelutskrift

Rad 1 Hej alla! Rad 2 Vår exempelfil består av tre rader. Rad 3 Det här är den sista raden. Radernas totallängd: 63

Det finns en radbrytning \n i slutet av varje rad i filen, men print-funktionen lägger också automatiskt till en rad i slutet av utskriften. Det finns inga extra radbyten i utskriften ovan eftersom radbrytningarna avlägsnats med hjälp av replace-metoden. Metoden ersätter alla radbrytningstecken med en tom sträng. I och med detta räknas radernas längder också korrekt.

Loading

Om Visual Studio Code inte hittar min fil?

När du kör din kod är det möjligt att Visual Studio Code meddelar att filen inte finns – även efter att du kollat att filen finns och att namnet är korrekt skrivet. Att ändra på följande inställning kan lösa problemet:

  • öppna inställningarna från menyraden: File -> Preferences -> Settings
  • sök efter den inställning som ska ändras med sökordet "executeinfile"
  • välj fliken Workspace
  • bocka i valet under Python -> Terminal -> Execute in file dir.

Inställningsfönstret borde se ut ungefär så här:

6 1 1

Om det här inte fungerar kan du kopiera filen i src-mappen…

6 1 2

…direkt till roten av uppgiftsmappen:

6 1 3

Att debugga kod som behandlar filer

När man använder Visual Studio Codes debuggare med program som behandlar filer, kan man stöta på följande felmeddelande:

6 1 4

Orsaken är att debuggaren alltid söker efter filer i roten av uppgiftsmappen. Inställningen Execute in file dir som nämndes ovan har ingen påverkan här. Den enklaste lösningen är att kopiera filen till rotmappen.

Du kan också behöva starta om Visual Studio Code efter att du har kopierat alla filer som behövs.

Läsa CSV-filer

En CSV-fil (kommaseparerade värden) är en textfil som innehåller data som separerats med ett visst tecken. Det här tecknet är vanligtvis komma (,) eller semikolon (;), men vilket som helst tecken är i princip möjligt.

CSV-filer är ett vanligt sätt att lagra olika typer av data. Flera databaser och kalkylprogram – exempelvis Excel – kan importera och exportera data i CSV-format. Det här möjliggör enkel dataöverföring mellan olika system.

Vi har redan bekantat oss med hur man kan gå igenom rader i en fil med en for-loop, men hur kan vi separera fält på en och samma rad? I Python kan vi använda strängmetoden split för detta. Metoden tar separatortecknet eller -tecknen som ett strängargument och returnerar innehållet i den ursprungliga strängen som en lista av strängar – separerade vid separatortecknen.

Följande exempel visar hur det fungerar:

text = "apa,banan,cembalo"
ordlista = text.split(",")
for ord in ordlista:
    print(ord)
Exempelutskrift

apa banan cembalo

Låt oss säga att vi har filen vitsord.csv, som innehåller namn på elever samt vitsord de fått i olika kurser. Varje rad har data som tillhör en studerande och olika data separeras med semikolon.

Exempeldata

Peter;5;4;5;3;4;5;5;4;2;4 Pauline;3;4;2;4;4;2;3;1;3;3 Pia;4;5;5;4;5;5;4;5;4;4

Följande program går igenom filen rad för rad, delar upp raderna i delar och skriver ut elevernas namn och vitsord:

with open("vitsord.csv") as fil:
    for rad in fil:
        rad = rad.replace("\n", "")
        delar = rad.split(";")
        namn = delar[0]
        vitsord = delar[1:]
        print("Namn:", namn)
        print("Vitsord:", vitsord)
Exempelutskrift

Namn: Peter Vitsord: ['5', '4', '5', '3', '4', '5', '5', '4', '2', '4'] Namn: Pauline Vitsord: ['3', '4', '2', '4', '4', '2', '3', '1', '3', '3'] Namn: Pia Vitsord: ['4', '5', '5', '4', '5', '5', '4', '5', '4', '4']

Loading
Loading

Läsa samma fil flera gånger

Ibland kan man behöva läsa innehållet i en fil flera gånger i samma program. Vi tittar på ett program som behandlar data om några personer i en CSV-fil:

Exempeldata
Peter;40;Helsingfors Emilia;34;Esbo Erik;42;Åbo Antonia;100;Helsingfors Lisa;58;Suonenjoki
with open("personer.csv") as fil:
    # skriver ut namn
    for rad in fil:
        delar = rad.split(";")
        print("Namn:", delar[0])

    # söker efter den äldsta personen
    hogsta_aldern = -1
    for rad in fil:
        delar = rad.split(";")
        namn = delar[0]
        alder = int(delar[1])
        if alder > hogsta_aldern:
            hogsta_aldern = alder
            aldst = namn
    print("Den äldsta är", aldst)

När vi kör programmet får vi det här felmeddelandet:

Traceback (most recent call last):
    print("Den äldsta är"; aldst)
UnboundLocalError: local variable 'aldst' referenced before assignment

Orsaken till felet är att den andra for-loopen aldrig körs, eftersom filen endast kan behandlas en gång. När den sista raden har lästs stannar file handlen i slutet av filen, filen stängs och vi kan inte längre komma åt filens innehåll.

Om vi vill komma åt innehållet även i den andra for-loopen, måste vi öppna filen på nytt:

with open("personer.csv") as fil:
    # skriver ut namn
    for rad in fil:
        delar = rad.split(";")
        print("Namn:", delar[0])

with open("personer.csv") as fil:
    # söker efter den äldsta personen
    hogsta_aldern = -1
    for rad in fil:
        delar = rad.split(";")
        namn = delar[0]
        alder = int(delar[1])
        if alder > hogsta_aldern:
            hogsta_aldern = alder
            aldst = namn
    print("Den äldsta är", aldst)

Även om den ovanstående koden fungerar, innehåller den onödig upprepning. Det lönar sig vanligtvis att läsa filen bara en gång, och spara dess innehåll i ett lämpligt format för fortsatt behandling:

personer = []
# vi läser in personerna till listan
with open("personer.csv") as fil:
    for rad in fil:
        delar = rad.split(";")
        personer.append((delar[0], int(delar[1]), delar[2]))

# skriver ut namn
for person in personer:
    print("Namn:", person[0])

# söker efter den äldsta personen
hogsta_aldern = -1
for person in personer:
    namn = person[0]
    alder = person[1]
    if alder > hogsta_aldern:
        hogsta_aldern = alder
        aldst = namn
print("Den äldsta är", aldst)

Mera om att behandla CSV-filer

Vi fortsätter behandla filen vitsord.csv, som innehåller följande data:

Exempeldata

Peter;5;4;5;3;4;5;5;4;2;4 Pauline;3;4;2;4;4;2;3;1;3;3 Pia;4;5;5;4;5;5;4;5;4;4

Följande program skapar lexikonet vitsord baserat på innehållet i filen. Nycklarna är elevernas namn och värdena innehåller elevernas respektive vitsord. Programmet konverterar vitsorden till heltal så att de kan hanteras enklare.

vitsord = {}
with open("vitsord.csv") as fil:
    for rad in fil:
        rad = rad.replace("\n", "")
        delar = rad.split(";")
        namn = delar[0]
        vitsord[namn] = []
        for givet_vitsord in delar[1:]:
            vitsord[namn].append(int(givet_vitsord))

print(vitsord)
Exempelutskrift

{'Peter': [5, 4, 5, 3, 4, 5, 5, 4, 2, 4], 'Pauline': [3, 4, 2, 4, 4, 2, 3, 1, 3, 3], 'Pia': [4, 5, 5, 4, 5, 5, 4, 5, 4, 4]}

Nu kan vi skriva ut statistik om varje studerande, baserat på värdena i lexikonet:

for namn, lista in vitsord.items():
    basta = max(lista)
    medeltal = sum(lista) / len(lista)
    print(f"{namn}: bästa vitsordet {basta}, medeltal {medeltal:.2f}")
Exempelutskrift

Peter: bästa vitsordet 5, medeltal 4.10 Pauline: bästa vitsordet 4, medeltal 2.90 Pia: bästa vitsordet 5, medeltal 4.50

Ta en titt på programmet i exemplet ovan. Det kan kanske verka aningen komplicerat vid den första anblicken, men tekniken kan användas med flera olika typer av data.

Ta bort överflödiga rader, mellanslag och radbrytningar

Låt oss säga att vi har en CSV-fil med namn, exporterar från Excel:

förnamn; efternamn
Peter; Python
Jaana; Java
Heikki; Haskell

Excel är ökänt för att lägga till extra mellanrum lite här och där. Här har vi ett extra mellanrum mellan elementen, efter varje semikolon.

Vi skulle vilja skriva ut efternamnet på varje person som finns i listan. Den första raden i filen innehåller information om den data som följer och kan skippas:

efternamn = []
with open("personer.csv") as fil:
    for rad in fil:
        delar = rad.split(";")
        # skippar raden med rubriker
        if delar[0] == "förnamn":
            continue
        efternamn.append(delar[1])

print(efternamn)

När koden körs får vi den här utskriften:

Exempelutskrift

[' Python\n', ' Java\n', ' Haskell']

De två första elementen har ett radbrytningstecken i slutet och alla tre element har ett mellanslag i början. Vi har redan använt replace-metoden för att ta bort onödigt mellanrum, men ett bättre sätt är strip-metoden hos strängar. Den här metoden tar bort tomrum från början och slutet av en sträng. Till tomrum räknas blanktecken, radbrytningar, samt tabb- och andra tecken som normalt inte skulle skrivas ut.

Vi kan testa metoden i Python-terminalen:

>>> " prov ".strip()
'prov'
>>> "\n\ntest\n".strip()
'test'
>>>

Att ta bort de onödiga tecknen kräver bara en liten ändring i programmet:

efternamn = []
with open("personer.csv") as fil:
    for rad in fil:
        delar = rad.split(';')
        if delar[0] == "förnamn":
            continue # skippar raden med rubriker
        efternamn.append(delar[1].strip())
print(efternamn)

Nu får vi den önskade utskriften:

Exempelutskrift

['Python', 'Java', 'Haskell']

Strängmetoderna lstrip och rstrip fungerar på motsvarande sätt som strip, men gör det då bara för antingen vänstra (l) eller högra (r) sidan av strängen:

>>> " teststräng  ".rstrip()
' teststräng'
>>> " teststräng  ".lstrip()
'teststräng  '

Kombinera data från olika filer

Det är mycket vanligt att data som behöver hanteras av ett program finns lagrade i flera filer. Vi tar en titt på ett exempel där personalens information i ett företag finns i filen personal.csv:

personnr;namn;adress;adressort
080488-123X;Peter Mikkola;Filpusvägen 7;00700 HELSINGFORS
290274-044S;Lisa Marttinen;Mannerheimvägen 100 A 10;00100 HELSINGFORS
010479-007Z;Arto Vihavainen;Tiilitehtaankatu 10;04260 KERAVA
010499-345K;Leevi Hellas;Tapiolavägen 9;02100 ESBO

Löneuppgifterna finns i en separat fil, lon.csv:

personnr;lön;bonus
080488-123X;3300;0
290274-044S;4150;200
010479-007Z;1300;1200

Alla rader i båda filerna innehåller en personlig id-kod (pic) som identifierar vems data vi arbetar med. När vi använder det här id:t som gemensam faktor, är det lätt att koppla en arbetstagares namn till hens lön. Vi kan då till exempel skriva ut en lista över de månatliga inkomsterna:

Exempelutskrift
inkomster:
Peter Mikkola    3300 euro
Lisa Marttinen   4350 euro
Arto Vihavainen  2500 euro

Programmet använder två lexikon som hjälpdatastrukturer: namn och loner. Båda använder personnr som nyckel:

namn = {}

with open("personal.csv") as fil:
    for rad in fil:
        delar = rad.split(';')
        if delar[0] == "personnr":
            continue
        namn[delar[0]] = delar[1]

loner = {}

with open("lon.csv") as fil:
    for rad in fil:
        delar = rad.split(';')
        if delar[0] == "personnr":
            continue
        loner[delar[0]] = int(delar[1]) +int(delar[2])

print("inkomster:")

for personnr, person in namn.items():
    if personnr in loner:
        lon = loner[personnr]
        print(f"{person:16} {lon} euro")
    else:
        print(f"{person:16} 0 euro")

Först skapar programmet lexikonen namn och loner. De har dessa innehåll:

{
    '080488-123X': 'Peter Mikkola',
    '290274-044S': 'Lisa Marttinen',
    '010479-007Z': 'Arto Vihavainen',
    '010499-345K': 'Leevi Hellas'
}

{
    '080488-123X': 3300,
    '290274-044S': 4350,
    '010479-007Z': 2500
}

´for´-loopen i slutet av programmet kombinerar arbetstagarnas namn med deras respektive löner.

Programmet kan också beakta situationer där personnumret saknas för en arbetstagare.

Kom ihåg att elementens ordning i lexikonet inte spelar någon roll, eftersom nycklarna behandlas med hjälp av hashvärden.

Loading
Loading
Loading
Loading
Loading
Loading
Loading...
:
Loading...

Log in to view the quiz

Du har nått slutet av den här delen! Fortsätt till nästa del:

Se dina poäng genom att klicka på cirkeln nere till höger av sidan.