Filer og CSV


Skrive til og lese fra fil

Vi kan åpne en fil ved å bruke syntaksen with ... as ... sammen med funksjonen open. Denne funksjonen tar inn et filnavn/sti til en fil samt en modus og returnerer et «filobjekt». Filobjektet kan vi bruke for å lese fra eller skrive til filen. For å skrive til filen bruker vi metoden write på filobjektet, mens for å lese fra filen bruker vi metoden read på filobjektet.

# Skrive til en fil
with open('minfil.txt', 'w', encoding='utf-8') as filobjekt:
    filobjekt.write('Hei, verden!')

# Lese fra en fil
with open('minfil.txt', 'r', encoding='utf-8') as filobjekt:
    innhold = filobjekt.read()
print(innhold) # Skriver ut 'Hei, verden!'

Noen vanlige moduser for filobjektet er:

I tillegg kan vi spesifisere om filen skal åpnes i tekstmodus eller binærmodus ved å legge til 't' eller 'b' i modus-strengen. For eksempel 'wt' eller 'wb'.

  • 't': åpner filen i tekstmodus (text). Dette er standard, og trenger derfor egentlig ikke å spesifiseres. Benytt denne modusen hvis du skal lese eller skrive tekst, som f. eks. .txt, .csv, .json, .html, .xml, .py etc.
  • 'b': åpner filen i binærmodus (binary). Dette er nødvendig hvis du skal lese eller skrive binære filer (f. eks. bilder, lyd, video, etc.). Vi vil ikke bruke denne modusen i dette emnet.

Den navngitte parameteren encoding= bør alltid spesifiseres, ellers kan du få problemer når programmet kjøres på et annet operativsystem. Dersom du skriver til en fil, bør du alltid spesifisere encoding='utf-8'. Dersom du leser fra en fil, må du benytte samme koding som ble brukt da filen ble skrevet. Les mer i kursnotater om unicode.

Hjelp, filen blir ikke funnet

Når du kjører et Python-program, kjører programmet «i» en mappe som kalles current working directory (cwd). Du kan se hvilken mappe dette er med koden:

import os
cwd = os.getcwd()
print(cwd)

Denne mappen blir bestemt av programmet som starter python. F. eks. hvis du bruker VSCode for å starte python, vil terminalen være i den samme mappen som VSCode er åpnet i (den som er nevnt med STORBOKSTAVER i filutforskeren til venstre). Cwd har altså ikke noen sammenheng med hvilken mappe filen som kjøres ligger i.

Når python får beskjed om å åpne en fil, vil den tolke filstien som blir oppgitt relativt til cwd. For eksempel, hvis filstien er kun et filnavn, antas det at filen ligger i cwd.

La oss si at vi har følgende filstruktur:

topfolder/
    foo.txt
    subfolder/
        myscript.py
        qux.txt

I skriptet myscript.py har vi følgende kode:

with open('bar.txt', 'w', encoding='utf-8') as f:
    print(f.write('Hello from bar.txt!'))

Hvor vil da filen bar.txt bli opprettet? Svaret er: det kommer an på.

  • Hvis du kjører myscript.py fra topfolder (altså hvis cwd er topfolder), vil filen bar.txt bli opprettet i topfolder.
  • Hvis du kjører myscript.py fra subfolder (altså hvis cwd er subfolder), vil filen bar.txt bli opprettet i subfolder.

Dersom du kjører programmet fra VSCode, vil cwd være den mappen du har åpnet VSCode i. Hvis vi har åpnet VSCode i topfolder, ser filutforskeren i VSCode slik ut:

Illustrasjon av filutforskeren i VSCode hvis programmet har åpnet topfolder

Da filen bar.txt vil da bli opprettet mappen topfolder – til tross for at kildekoden befinner seg i mappen subfolder.

La oss si at vi har følgende filstruktur:

inf100/
    lab5/
    lab6/
    lab7/
        check_valid_word.py
        wordlist.txt

I skriptet check_valid_word.py har vi følgende kode:

with open('wordlist.txt', 'r', encoding='utf-8') as f:
    content = f.read()
...

Programmet krasjer med følgende feilmelding:

  File "/your/path/to/inf100/lab7/findword.py", line 1, in <module>
    with open('wordlist.txt', 'r', encoding='utf-8') as f:
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
FileNotFoundError: [Errno 2] No such file or directory: 'wordlist.txt'

Hva er feilen? Svaret er sannsynligvis: du kjører programmet fra feil mappe (cwd er altså ikke /path/to/inf100/lab7). Kan det for eksempel være at du kjører programmet fra inf100 -mappen?

Feilsøkingssteg:

  • Sjekk hva cwd er ved å legge til følgende linjer i programmet: import os og print(os.getcwd())

  • Hvis cwd ikke er /your/path/to/inf100/lab7: åpne VSCode i lab7 -mappen (File -> Open Folder -> velg lab7 -mappen) og kjør programmet nå.

  • Alternativt kan du navigere til lab7 -mappen med kommandoer i terminalen og kjøre programmet derfra.

Det er mulig å programmatisk endre cwd til å bli samme mappe som filen som kjøres ligger i:

import os
directory_of_current_file = os.path.dirname(__file__)
os.chdir(directory_of_current_file) # endrer cwd

Dette kan kanskje gjøre ting lettere i utviklingsfasen og for raske og enkle formål, men er sannsynligvis ikke noe en erfaren programmerer ville ønsket seg, siden man da må flytte hele programmet hvis man vil bruke det i en annen mappe.

Enkel CSV -håndtering

En CSV-fil er en tekstfil som inneholder tabell-data. CSV står for «comma separated values», og det er nettopp det det er: en tekstfil hvor hver linje inneholder en rekke verdier som er separert med komma (eller et annet symbol). Hver linje i filen representerer en rad i tabellen, og hver verdi representerer en kolonne i tabellen.

Regneark i Microsoft Excel eller Google Sheets kan lagres som CSV-filer. Dette er et vanlig format for å utveksle data mellom ulike programmer.

Innholdet i en CSV-fil (people.csv) kan se slik ut:

Navn,Alder,Høyde
Ola,20,1.80
Kari,19,1.65
Per,21,1.73
Oda,20,1.74

Det finnes biblioteker i Python som er spesielt laget for å lese CSV-filer, men i dette avsnittet skal vi vise hvordan vi kan lese dem helt selv. En csv-fil er nemlig bare en tekstfil, og vi kan lese den på akkurat samme måte som vi leser andre tekstfiler.

###########################################
### LESE INPUT OG OPPRETTE DATASTRUKTUR ###
###########################################

with open('people.csv', 'r', encoding='utf-8') as file_object:
    # content_string er streng som inneholder hele innholdet i filen
    content_string = file_object.read()

# .strip fjerner whitespace på begynnelsen og slutten av strengen
content_string = content_string.strip() 

# .split('\n') klipper opp strengen ved linjeskift, og gir oss en
# liste med bitene som er igjen
content_lines = content_string.split('\n')

# Vi oppretter en 2D-liste (en liste av lister) som skal inneholde
# tabellen vår
table = []
for line in content_lines:
    # .split(',') klipper opp strengen ved komma, og gir oss en
    # liste med bitene som er igjen
    values = line.split(',')
    table.append(values)


# Vi kan nå aksessere enkeltverdier i tabellen vår ved å bruke
# indeksering på samme måte som vi gjør med andre lister
print(table[0][1]) # Alder
print(table[1][0]) # Ola
print(table[3][2]) # 1.73

# Ofte gir det mening å ha overskriftene og selve dataene i separate
# variabler.
headers = table[0] # første rad
data = table[1:]  # alle rader unntatt den første

##############################################
### UTFØR SELVE DATABEHANDLINGEN VI ØNSKER ###
##############################################

# Et år har passert! Øk alle aldre med 1 i datasettet.
for row in data:
    row[1] = 1 + int(row[1]) # PS: dette endrer typen til int


############################
### PRESENTER RESULTATET ###
############################

# Skriv den endrede tabellen til en ny fil
with open('people_a_year_later.csv', 'w', encoding='utf-8') as file_object:
    for row in table:
        # Konverterer alle cellene i listen til strenger
        string_row = [str(x) for x in row]

        # .join() limer sammen strenger i en liste til én stor streng
        # med den gitte limestrengen mellom hver av dem
        line = ','.join(string_row)
        file_object.write(line + '\n')