Les og utforsk



Denne uken skal vi lese data fra en fil. Se på Section 7.2 og 7.2.1 i Python Tutorial: https://docs.python.org/3/tutorial/inputoutput.html#reading-and-writing-files

Les også gjennom kapittelet om Exceptions fra Pythons egen tutorial: https://docs.python.org/3/tutorial/errors.html


Lese fra en fil

Python kommer med innebygde funksjoner som gjør det enkelt å skrive og lese filer. I dette eksemplet skal vi prøve å lese og skrive til en fil. Du må laste ned tekstfilen: timemachine.txt og ha dem i samme mappe på datamaskinen din.

Hvis du støter på en FileNotFoundError, kan du sjekke ut Hjelp-filen-blir-ikke-funnet -delen for mulige løsninger.

Her er tre måter å få informasjon fra en fil i Python:

  1. Den vanligste måten er bruk av en for-løkke:
with open("timemachine.txt", encoding="utf8") as f:
    for line in f:
        line = line.strip()  # removes spaces before and after string
        l = len(line)
        print("The line is", l, "long, and ends with")
        print(">>>", line[-20:], "<<<")
  1. Bruk av readlines()-metoden:

with open("timemachine.txt", encoding="utf8") as f:
    all_lines = f.readlines()

print(len(all_lines), "lines in the text")
  1. Bruk av read()-metoden:
with open("timemachine.txt", encoding="utf8") as f:
    all_lines = f.read()

print("vvvvvvv")
print(all_lines)
print("^^^^^^^")

Hva er forskjellen mellom for line in f, f.readlines() og f.read() for å lese fra filen?

Skrive til en fil
with open("numbers.txt", "w", encoding="utf8") as f:
    for i in range(100):
        f.write(f"{i} is a nice number\n")
# now open the file numbers.txt in your editor and see what's inside

# we can also use .writelines() to write to the file
with open("numbers2.txt", "w", encoding="utf8") as f:
    # here we use a list comprehension, could also make this a regular loop
    lines = [f"{i} is a nice number\n" for i in range(100)]
    f.writelines(lines)

# open the file numbers2.txt in your editor and see what's inside

# write other files!
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 terminalen 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 venstre menyen). Mappen terminaler er i 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 du bruker funksjonskallet read_file("foo.txt") og ønsker å åpne filen foo.txt, som ligger i samme mappe som python-filen du kjører, la oss si mappen labX. La oss videre tenke oss at labX i sin tur ligger i mappen inf100, og det er den sistnevnte mappen du har åpnet med VSCode. Da vil programmet krasje med en FileNotFoundError.

For å klare å åpne filen foo.txt ved å kjøre python fra VSCode, kan du gjøre ett av fire tiltak:

Data processing

Når en fil har blitt åpnet i Python, har vi tilgang til innholdet, og kan bruke alt av python-funksjonaliteten for å jobbe med det. Vi kan for eksempel behandle innholdet ved å sortere, filtrere eller søke gjennom dataene.

Her får vi en tekstfil med makstemperaturer i løpet av en uke: temperatures.txt. Vi vil finne ut den høyeste og laveste temperaturen i løpet av uken. For å gjøre det leser vi først linjene fra filen og lagrer dem i en liste med tupler. Vi putter temperaturen først i tuplene fordi vi kan da direkte bruke sort() til å sortere. sort() vil sortere med hensyn til tuplenes første elementer i en liste med tupler. Dette er input-delen av programmet vårt der vi henter inn informasjon fra filen og velger en nyttig datastruktur.

Når vi har en liste med tupler sorterer vi den for å enkelt finne høyeste og laveste temperaturen. Dette er dataanalysedelen av programmet vårt, der vi jobber med dataene. Ved å holde denne delen avskilt, har vi mulighet å gjenbruke den med ulike datakilder.

Etter det presenterer vi resultatet ved å skrive ut på skjermen hvilken som var den kaldeste og den varmeste dagen i uken. Dette er presentasjonsdelen av programmet vårt, der vi presenterer resultatene. Det er igjen nyttig å holde denne separat fra analysedelen, det gir oss mulighet å skrive ut resultatene på andre måter enn bare å printe i terminalen.

# IO-part (placing the data into a data structure)
# =====================================================

with open("temperatures.txt", encoding="utf8") as f:
    temp_day = []
    for line in f:
        day, temp = line.split()
        temp_day.append((temp, day))

# =====================================================

# data processing/analysis
# =====================================================

# this will sort according to ascending temperature
temp_day.sort()
# print(temp_day)

# =====================================================


# IO-part (presenting the results)
# =====================================================

# day with the lowest temperature
print(
    f"The coldest day was {temp_day[0][1]} with a temperature of {temp_day[0][0]} °C."
)

# day with the highest temperature
print(
    f"The warmest day was {temp_day[-1][1]} with a temperature of {temp_day[-1][0]} °C."
)

# =====================================================

I dette eksempelet teller vi hvor mange ganger ulike bokstaver og ord blir brukt i hele boken ’Alice in wonderland’. Vi skal lese inn teksten fra en fil, istedenfor å lime inn hele teksten i python-filen, som vi gjorde før.

Til dette eksemplet må du laste ned denne tekstfilen: alice.txt.

# IO-part (placing the data into a data structure)
# =====================================================

with open("alice.txt", encoding="utf8") as f:
    text = f.read()

# =====================================================


# data processing/analysis
# =====================================================

letter_count = {}

# opprett 0-verdi til alle bokstav som skal telles
for l in "abcdefghijklmnopqrstuvwxyz":
    letter_count[l] = 0

# sjekk hvordan dict ser ut
# print(letter_count)

for let in text:
    let = let.lower()
    if let in letter_count:
        letter_count[let] += 1

# =====================================================

# IO-part (presenting the results)
# =====================================================

for let, count in letter_count.items():
    print(f"{let} is used {count:5d} times")


print("\n\n\n")

# =====================================================

# data processing/analysis
# =====================================================

word_count = {}

for word in text.split():
    word = word.lower()
    word_count.setdefault(word, 0)
    word_count[word] += 1


def get_second(tpl):
    return tpl[1]


sorted_result = sorted(word_count.items(), key=get_second, reverse=True)

# =====================================================

# IO-part (presenting the results)
# =====================================================

N = 15
print(f"The {N} most common words")
for w, c in sorted_result[:N]:
    print(f"{w:14} is used {c:5d} times")

# =====================================================

Unntak / Try og except

Krasj av programmet kan være en bra ting, fordi vi ønsker å vite at noe er feil så fort som mulig. Men i mange tilfeller ønsker vi at programmet skal håndtere krasjen selv; dette gjelder f.eks når vi vet på forhånd hva slag krasj som kan oppstå når en bruker tar feil. Exceptions er ikke et lurt triks å bruke dette for å skyve problemer under teppet.

# Håndtere en krasj
# Prøv å gi programmet noe som ikke er et tall
# Prøv å gi programmet et all som er for stort
# Programmet krasjer ikke selv om brukeren gir ugyldige svar!

animals = ["katt", "hund", "kanin", "hamster", "krokodille"]


try:
    i = int(input(f"Velg ett tall [1-{len(animals)}]: "))
    animal = animals[i-1]
except:
    # Kjøres dersom try-blokken krasjet
    print("Ugyldig valg!")
else:
    # Kjøres dersom try-blokken gikk bra
    print("Gratulerer, du fikk en ny", animal)

print("Nå er programmet ferdig")

Bruk except med varsomhet. Å bruke bare en except: uten navnet til feilen kan gjøre koden din litt vanskeligere å feilsøke:

# FARE!! except håndterer for mange krasjer! Vanskelig å feilsøke!
# Prøv å gi programmet en gyldig tall som input

animals = ["katt", "hund", "kanin", "hamster", "krokodille"]


try:
    i = int(input(f"Velg ett tall [1-{len(animals)}]:"))
    animal = animal[i-1] # Skrivefeil! Vi VIL krasje her med NameError!
except:
    print("Ugyldig valg!") # Oops! Kommer hit selv om input er gyldig
else:
    print("Gratulerer, du fikk en ny", animal)

print("Nå er programmet ferdig")

Man bør alltid spesifisere hvilken type krasj man håndeterer.

# Håndere ulike typer feil
try:
    i = int(input(f"Velg ett tall [1-{len(animals)}]:"))
    animal = animals[i-1]
except ValueError:
    print("Ugyldig valg, du må oppgi et tall!")
except IndexError:
    print("Tallet du oppgav er ugyldig!")
else:
    print("Gratulerer, du fikk en ny", animal)

print("Nå er programmet ferdig")

# NameError blir ikke fanget av except nå,
# så vi oppdager det nå hvis variabeler har feil navn.
Krasje på egen hånd

Kodeordet raise brukes for å krasje på egen hånd.

food = input("Hva skal vi spise? ")
if food not in ["salat", "tomat", "agurk", "paprika"]:
    raise ValueError(f"Maten '{food}' er ikke akseptabel.")

print("Takk for maten!")

Kan brukes for å gi ekstra informasjon ved krasj

def foo(i, j):
    y = j
    for x in range(i, j):
        try:
            y += abs(y/x)
        except ZeroDivisionError as err: # err variabel som 'husker' krasjen
            # Skriver ut debug-informasjon
            print("En feil har oppstått:", err)
            print("Lokale variabler: ", locals())
            # Vi kan også kaste den samme krasj videre: 
            raise # prøv å fjerne denne og se forskjellen
    return y

print(foo(-5, 10))