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: :download:`timemachine.txt` Last ned filen her: :download:`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? .. literalinclude:: eksempel_1.py 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: :download:`alice.txt`. Koden er i denne filen: :download:`eksempel_2.py`. Kjør koden. Skjønner du hva som skjer? .. literalinclude:: eksempel_2.py 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: :download:`eksempel_3.py` og kjør den. Skjønner du forskjellen mellom de ulike måtene å bruke ``except``? .. literalinclude:: eksempel_3.py 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: :download:`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()``? .. literalinclude:: eksempel_4.py 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: :download:`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? .. literalinclude:: eksempel_5_1.py Last ned filen her: :download:`eksempel_5_2.py` som er samme kode som :download:`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. .. literalinclude:: eksempel_5_2.py 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: :download:`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? .. literalinclude:: eksempel_6.py Oppgaver -------- .. note:: Du kan bruke :download:`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 :download:`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! .. math:: \begin{bmatrix} a_{1} \\ a_{2} \\ \vdots \\ a_{n} \end{bmatrix} \cdot \begin{bmatrix} b_{1} \\ b_{2} \\ \vdots \\ b_{n} \end{bmatrix} = (a_1 \cdot b_1) + (a_2 \cdot b_2) + \dots + (a_n \cdot b_n) 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 9 - Filnavn (basert på eksamen 20V) ------------------------------------------- I løpet av forskningsprosjektet ditt har du samlet inn mange datafiler. Alle filer har .txt format, og de første linjene i hver fil inneholder sted og tidspunkt hvor og når data ble samlet inn. Akkurat nå har filene tilfeldige navn (qwghlm, qwerty, ...), som gjør det vanskelig å finne et datasett uten å åpne alle filene. Du vil gjerne gi bedre navn til filene, basert på informasjonen som finnes i hver fil. For eksempel, en fil som heter :download:`qwghlm.txt` starter med:: Tromso 2020-05-07 3.141 2.7172 4.567 1.234 2.7172 4.567 1.234 9.8165 [... mange flere data-linjer ...] og filen :download:`qwerty.txt` starter med:: Oslo 2019-06-01 -5.141 8.7272 -4.567 1.2364 12.7172 44.5367 1.234 9.81372 [...] **Skriv en funksjon rename_from_data(filename)** som * tar inn et filnavn som argument, * leser inn filen som er nevnt, * tar sted og dato fra de første to linjene * lager et nytt filnavn fra dato og sted, med .txt til slutt. Formatet skal være ``YYYY-MM-DD_PLACENAME.txt`` * lagrer all data fra den opprinnelige filen (uten sted og tid) inn i en ny fil med det nye filnavnet **Skriv en funksjon rename_all(namelist)** som tar inn en liste med filnavn og bruker rename_from_data() på hver fil i listen. I eksempelet vårt inneholder namelist listen ["qwghlm.txt","qwerty.txt"]. Funksjonen din skal gå gjennom listen og lagre en ny fil for hver gammel fil. Innholdet av "qwghlm.txt" skal lagres som "2020-05-07_Tromso.txt" og innholdet av "qwerty.txt" skal lagres som "2019-06-01_Oslo.txt". Oppgave 10 - Havnivå -------------------- *Du skal ikke bruke eksterne biblioteker her, men du kan legge til flere hjelpefunksjoner* I filen :download:`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.