Uke 10 - Files og Exceptions¶
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
Eksempler - Files¶
Eksempel 1¶
I dette eksemplet skal vi prøve å lese og skrive til en fil. Du må laste ned denne
tekstfilen: timemachine.txt
Last ned filen her: eksempel_1.py
, og kjør koden. Hva er forskjellen mellom
for line in f
, f.readlines()
og f.read()
for å lese fra filen?
Hva skjer når du åpner filen ’numbers.txt’ med mode ’w’ i slutten?
# 3 ways of getting information from a file
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:],'<<<')
# ========
with open('timemachine.txt', encoding="utf8") as f:
all_lines = f.readlines()
print(len(all_lines),'lines in the text')
# ==========
with open('timemachine.txt', encoding="utf8") as f:
all_lines = f.read()
print("vvvvvvv")
print(all_lines)
print("^^^^^^^")
# ========
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
# write other files!
Eksempel 2¶
I dette eksempelet teller vi hvor mange ganger ulike bokstaver og ord blir brukt i hele boken ’Alice in wonderland’. Derfor vil vi lese in teksten fra en fil, istedet for å lime hele teksten inn i filen med python-kode, som vi gjorde før.
Til dette eksemplet må du laste ned denne tekstfilen: alice.txt
.
Koden er i denne filen: eksempel_2.py
. Kjør koden. Skjønner du
hva som skjer?
with open('alice.txt', encoding="utf8") as f:
text = f.read()
### resten er lik programmet fra før
letter_count = {}
for l in 'abcdefghijklmnopqrstuvwxyz':
letter_count[l] = 0
for let in text:
let = let.lower()
if let in letter_count:
letter_count[let] += 1
for let, count in letter_count.items():
print(f'{let} is used {count:5d} times')
print('\n\n\n')
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)
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')
Eksempler - Exceptions¶
Eksempel 3¶
En vanlig type feil når man bruker lister er IndexError. Det får man når man prøver å hente et element fra et index som er utenfor listen.
Her er et eksempel på dette. Prøv å kjøre denne koden:
xs = ['a', 'b', 'c']
a = xs[5] # fails with IndexError
print(a)
Vi kan håndtere feilet ved hjelp av try-except
. Dette kan vi gjøre på
noen forskjellige måter. Vi kan enten fange alle feil med å bare bruke
except
eller så kan vi bare fange IndexErrors ved å bruke except IndexError
.
Vi kan også fange både IndexErrors og TypeErrors ved å bruke except IndexError
og except TypeError
etter hverandre.
Her er eksempel på dette. Last ned koden her: eksempel_3.py
og kjør den. Skjønner du forskjellen mellom de ulike måtene å bruke
except
?
xs = ['a', 'b', 'c']
# print(xs[5]) # fails with IndexError
#Option 1 - catch everything
try:
x1 = xs[2]
x2 = xs[5]
print(x2 - x1)
except:
print('Something went wrong')
print('Decide what to do next here')
# ...
#Option 2 - catch specific
try:
x1 = xs[2]
x2 = xs[5] # broken
print(x2 - x1)
except IndexError as err:
print('We caught an IndexError:', err)
#Option 3 - TypeError still goes out
try:
x1 = xs[2]
x2 = xs[0]
print(x2 - x1) # broken
except IndexError as err:
print('We caught an IndexError:', err)
# get TypeError in Terminal
#Option 4 - both caught
try:
x1 = xs[2]
x2 = xs[0]
print(x2 - x1) # broken
except IndexError as err:
print('We caught an IndexError:', err)
except TypeError as err:
print('We caught a TypeError:', err)
Eksempel 4¶
I dette eksemplet skal vi lese inn tall fra en fil. Om det er noen feil i filen som blir leset inn kommer vi få en error i koden. Dette håndterer vi med try-except.
Last ned koden her: eksempel_4.py
. Her fanger vi FileNotFoundError
og ValueError for seg selv og så alle andre feil. Kan det bli noen
andre feil enn FileNotFoundError og ValueError når vi bruker funksjonen
get_numbers_from_file()
?
def get_numbers_from_file(filename):
"""
Read numbers from a file.
The file format is a simple column like:
31
145
-947
If anything goes wrong, return an empty list
"""
try:
data = []
with open(filename) as f:
for line in f:
number = int(line)
data.append(number)
return data
except FileNotFoundError:
print(f"Warning: {filename} does not exist.")
return []
except ValueError:
print(f"Warning: {filename} contains items that are not integers.")
return []
except:
print("Warning: Unknown problem")
return []
# if __name__ == "__main__":
# make your own files and your own function calls to get_numbers_from_file() here
Prøv nå å lage dine egne filer og bruk funksjonen get_numbers_from_file()
i hovedprogrammet. Et kall av funksjonen skal være uten
feil, et skal gi FileNotFoundError og et skal gi ValueError.
Eksempel 5¶
Det er ofte mulig å plassere try-except på ulike steder i koden sin. Da må man velge det stedet hvor det er mest naturlig at et feil blir håndtert.
Last ned filen her: eksempel_5_1.py
. Funksjonen
all_lines_through_points()
skal printe ligningen for alle linjer
mellom alle fire punkter den får som input (som en liste). Om det ikke
er mulig å beregne ligningen mellom et par av punkter skal den printe
hvorfor dette ikke er mulig og siden gå videre til neste par (programmet
skal ikke krasje).
Om du prøver å kjøre koden så krasjer den. Plassere ut try-except hvor du synes det er mest naturlig, slik at koden fungerer som den skal. Hvorfor har du valgt å plassere try-except der du har plassert det?
def slope(x1, y1, x2, y2):
return (y2 - y1) / (x2 - x1)
def y_intercept(x1, y1, x2, y2):
return (x2 * y1 - x1 * y2) / (x2 - x1)
def line_eqn_from_points(p1, p2):
# p1, p2 are 2-tuples
x1, y1 = p1
x2, y2 = p2
a = slope(x1, y1, x2, y2)
b = y_intercept(x1, y1, x2, y2)
return f"y = {a}x + {b}"
def all_lines_through_points(points):
num_points = len(points)
for i in range(num_points):
for j in range(i + 1, num_points):
p1 = points[i]
p2 = points[j]
eq = line_eqn_from_points(p1, p2)
print(f"The equation for the line between {p1} and {p2} is {eq}.")
if __name__ == "__main__":
all_lines_through_points([
(1, 1),
(-1, 1),
(1, -1),
(-1, 1),
])
Last ned filen her: eksempel_5_2.py
som er samme kode som
eksempel_5_1.py
men med try-except, slik at koden ikke krasjer.
Her er try-except plassert i funksjonen all_lines_through_points()
.
Om try-except er i noen av funksjonene slope()
og y_intercept()
må vi si hva de funksjonene skal returnere om det ikke er mulig å beregne
linjens stigning eller konstantledd. Her finnes det ingen selvfølgelig svar.
Om funksjonene returnerer noen string med feilmelding må vi alltid sjekke
hva vi får når vi bruker funksjonene og se om vi får et tall eller en
feilmelding. Det er bedre om vi vet at vi alltid får et tall fra funksjonene,
om de ikke krasjer.
Det er samme med å plassere try-except i line_eqn_from_points()
. Da må
vi si hva den funksjonen skal returnere om det blir en error. Når vi så
bruker line_eqn_from_points()
må vi alltid sjekke om strengen vi får er
en feilmelding eller en ligning. Det er bedre å vite at all_lines_through_points()
alltid returnerer en ligning, om den ikke krasjer.
Men i funksjonen all_lines_through_4_points()
vet vi hva som skal skje
om det ikke går å beregne ligningen for linjen mellom to punkter. Da skal
vi printe en melding om dette og hvorfor det ikke er mulig. Derfor passer
det best å plassere try-except her.
def slope(x1, y1, x2, y2):
return (y2 - y1) / (x2 - x1)
def y_intercept(x1, y1, x2, y2):
return (x2 * y1 - x1 * y2) / (x2 - x1)
def line_eqn_from_points(p1, p2):
# p1, p2 are 2-tuples
x1, y1 = p1
x2, y2 = p2
a = slope(x1, y1, x2, y2)
b = y_intercept(x1, y1, x2, y2)
return f"y = {a}x + {b}"
def all_lines_through_points(points):
num_points = len(points)
for i in range(num_points):
for j in range(i + 1, num_points):
p1 = points[i]
p2 = points[j]
try:
eq = line_eqn_from_points(p1, p2)
print(f"The equation for the line between {p1} and {p2} is {eq}.")
except:
if p1 == p2:
print(
f"Cannot compute line equation between the identical points {p1} and {p2}."
)
else:
print(
f"Cannot compute line equation for vertical line between points {p1} and {p2}."
)
continue
if __name__ == "__main__":
all_lines_through_points([
(1, 1),
(-1, 1),
(1, -1),
(-1, 1),
])
# find place where you can explain _why_ the exception happened, not just _that_ it happened
Eksempel 6¶
Her er et eksempel hvor vi definerer en egen type error: NoSeat
. Vi bruker
den når det ikke finnes en ledig plass som oppfyller vilkårene fra oppgaven.
Vi bruker også try except til å sjekke at input er som den skal. Ellers spør vi brukeren om ny input.
Last ned koden her: eksempel_6.py
. Hvordan kan vi håndtere en plass
som ikke er tilgjengelig uten å bruke exceptions? Synes du det er bedre å
håndtere utilgjengelige seter med eller uten å bruke exceptions? Hvorfor?
class NoSeat(Exception):
pass
def seat_from_position(chart, row, col):
"""
Find out if seat at given position is available
Update seating chart (in place)
Return price of the seat
"""
price = chart[row - 1][col - 1]
if price == 0:
raise NoSeat
chart[row - 1][col - 1] = 0
return price
def seat_from_price(chart, price):
"""
Find available seat given a price
Update seating chart (in place)
Return seat position for an available seat of given price
"""
for row, line in enumerate(chart):
for col, seat in enumerate(line):
if seat == price:
chart[row][col] = 0
return row + 1, col + 1
raise NoSeat
def print_chart(chart):
"""
Print seating chart
"""
print()
for line in chart:
print(f"{line[0]:>2}", end="")
for seat in line[1:]:
print(f"{seat:>3}", end="")
print()
print()
def input_int_from_range(msg, min, max):
while True:
try:
ans = input(msg)
ans = int(ans)
if min <= ans <= max:
return ans
else:
print(f"{ans} is outside [{min},{max}]. Try again")
except ValueError:
print(f"Not an Integer: {ans}. Try again")
def ask_for_seat(chart):
row = input_int_from_range("Please specify row number (1-9): ", 1, 9)
col = input_int_from_range("Please specify column number (1-10): ", 1, 10)
price = seat_from_position(chart, row, col)
return price
def ask_for_price(chart):
price = input_int_from_range("Please specify a price: ", 10, 50)
row, col = seat_from_price(seating_chart, price)
return row, col
if __name__ == "__main__":
seating_chart = [
[10, 10, 10, 10, 10, 10, 10, 10, 10, 10],
[10, 10, 10, 10, 10, 10, 10, 10, 10, 10],
[10, 10, 10, 10, 10, 10, 10, 10, 10, 10],
[10, 10, 20, 20, 20, 20, 20, 20, 10, 10],
[10, 10, 20, 20, 20, 20, 20, 20, 10, 10],
[10, 10, 20, 20, 20, 20, 20, 20, 10, 10],
[20, 20, 30, 30, 40, 40, 30, 30, 20, 20],
[20, 30, 30, 40, 50, 50, 40, 30, 30, 20],
[30, 40, 50, 50, 50, 50, 50, 50, 40, 30],
]
print("Welcome! Here you can purchase seats for the theatre.")
print("Below are the prices for all the seats:")
print_chart(seating_chart)
print(
"""You can either specify a seat you would like to purchase,
or you can specify a price you want to pay for your seat.
You can buy as many seats as you like.\n"""
)
while True:
print("Below is the current seating chart:")
print_chart(seating_chart)
option = input(
"Type [s] to specify a seat, type [p] to specify a price or type [q] to quit: "
)
try:
if option == "q":
break
elif option == "s":
price = ask_for_seat(seating_chart)
print(f'The seat costs kr.{price}.')
elif option == "p":
row, col = ask_for_price(seating_chart)
print(f'You bought row {row}, seat {col}.')
else:
print("Please type either [s], [p] or [q].")
except NoSeat:
print("No free seats found that match your request, please try again.")
Oppgaver¶
Obs
Du kan bruke askeladden.txt
som en test fil, men funksjonene skal fungere med ulike filnavn)
Oppgave 1¶
Skriv en funksjon som heter def open_file(filename)
som åpner en fil med
navn filename
og returnerer én streng som inneholder hele filinnholdet.
Oppgave 2¶
Skriv en funksjon som heter def open_file(filename)
som åpner en fil med
navn filename
og lagrer innholdet til filen som én streng
med >>>
foran hver linje og <<<
etter. Returner denne strengen.
Eksempelresultat:
>>>Det var en gang en konge, og den kongen hadde hørt snakk om et skip som gikk like fort til lands som til vanns.<<<
>>>Så ville han ...
...
Oppgave 3¶
Skriv en funksjon som heter def open_file(filename)
som åpner en fil med
navn filename
, looper over linjene i filen,
og tar ut den første bokstaven til det siste ordet i hver linje.
Returner disse bokstavene som én streng.
Eksempelresultat:
'vlf'
Oppgave 4¶
Skriv en funksjon som heter def r_w_file(infile, outfile)
som åpner filen med navn infile
, looper over linjene på filen og lager en ny
fil med kun de linjene som inneholder ordet
lame. Denne nye filen skal ha navnet fra outfile
variablen.
Eksempelkjøring med in.txt
:
1 is lame
3 is lame
5 is lame
6 is lame
7 is lame
Oppgave 5¶
Skriv en funksjon def first_letters(filename)
som bruker open_file
fra oppgave 3.
Funksjonen skal ta et filnavn som argument og returnere en streng med den første
bokstaven i det siste ordet for hver linja av filen.
Hvis filen ikke finnes skal den returnere ""
.
Tips: Hvis en funksjon forsøker å åpne en fil som ikke finnes vil Python signalisere (raise) en FileNotFoundError.
Oppgave 6¶
Prikkproduktet av to vektorer er resultatet av å gange sammen hvert element, og så legge sammen disse. Men prikkproduktet er bare definert hvis vektorene er like lange!
Skriv en funksjon dot_product(a,b)
som tar to lister som argumenter
og returnerer prikkproduktet av disse.
Hvis det ikke går ann å ta prikkproduktet (listene ikke er like lange)
skal funksjonen signalisere (raise) en
ValueError.
Oppgave 7¶
Dette er en enkel funksjon, men den kan faile og kræsje programmet:
def add_together(a, b, c, d):
return a + b + c + d
Skriv en ny funksjon add_together_safely(a, b, c, d)
som tar 4 argumenter og bruker
denne funksjonen på dem.
Hvis ingenting går galt skal du returnere resultatet av add_together
.
Hvis noe går galt skal du skrive ut Failed with error:
og så feilmeldingen,
til slutt skal funksjonen returnere None
.
Eksempelkjøring (i interaktiv modus):
>>> add_together_safely(1, 2, 3, 4)
10
>>> add_together_safely('a', 'b', 'c', 'd')
'abcd'
>>> add_together_safely(1, 2, 'c', 'd')
Failed with error: unsupported operand type(s) for +: 'int' and 'str'
Oppgave 8¶
Skriv din egen versjon av dictionary-funksjonen get
.
Funksjonen skal hete my_get(d, k, v)
og ta tre argumenter: en dictionary, en nøkkel og en default-verdi.
Hvis nøkkelen finnes i dictionary skal den returnere verdien til nøkkelen,
og ellers skal den returnere default-verdien.
Bruk try/except
i løsningen din.
Oppgave 10 - Havnivå¶
Du skal ikke bruke eksterne biblioteker her, men du kan legge til flere hjelpefunksjoner
I filen VIK_sealevel_2000.txt
finnes
målinger av havnivået på et sted, hvor
en linje i filen representerer én måling, repetert for hver time i året 2000.
Det finnes 8784 linjene i alt, og formatet er
year month day hour height[cm]
Skriv en funksjon read_file(filename) som tar et filnavn av en slik datafil som argument og returnerer en liste av tuples med 5 heltall. Funksjonen din trenger ikke å fungere for andre datafiler, bare for ``VIK_sealevel_2000.txt``. I vår eksempelfil begynner og slutter listen slik:
[
(2000, 1, 1, 1, 335),
(2000, 1, 1, 2, 336),
(2000, 1, 1, 3, 338),
# ...
(2000, 12, 31, 22, 337),
(2000, 12, 31, 23, 338),
(2000, 12, 31, 24, 339)
]
Alle følgende funksjonene tar inn denne listen som data
argument.
Skriv en funksjon average(data, month=None) som tar inn datalisten
og et argument month
med standardverdi None
. Hvis month
er None
,
skal funksjonen returnere gjennomsnittet av alle målingene. Hvis month
er gitt
som et tall 1-12, skal funksjonen returnere gjennomsnittet av alle målingene
i den tilsvarende måneden.
Skriv en funksjon add_weekday(data) som tar inn datalisten og returnerer an ny dataliste
med 6 kolonner. Den 6. kolonnen skal inneholde ukedag som en streng (Sat Sun Mon Tue Wed Thu Fri
).
2000-01-01 var en lørdag (Sat
), 2000-12-31 var en søndag (Sun
).
I filen vår er målingene regelmessige, det finnes én linje for hver time av året, slik
at du kan anta 24 målinger for hvert døgn.
For eksempeldatasettet ser returnverdien slik ut:
[
(2000, 1, 1, 1, 335, 'Sat'),
(2000, 1, 1, 2, 336, 'Sat'),
(2000, 1, 1, 3, 338, 'Sat'),
# ...
(2000, 12, 31, 22, 337, 'Sun'),
(2000, 12, 31, 23, 338, 'Sun'),
(2000, 12, 31, 24, 339, 'Sun')
]
Skriv en funksjon average_weekday(data, weekday) som tar inn den utvidete datalisten
og et argument weekday
som er en av strengene Sat Sun Mon Tue Wed Thu Fri
.
Funksjonen skal returnere gjennomsnittet av alle målingene for tilsvarende ukedagen.