Uke 10 - Filer 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 - Filer ----------------- Eksempel 1 .......... I dette eksemplet skal vi prøve å lese og skrive til en fil. Du må laste ned tekstfilen: :download:`timemachine.txt` og Python-filen: :download:`eksempel_1.py` og ha dem i samme mappe på datamaskinen din. Åpne så mappen i VSCode. .. note:: Åpne ``timemachine.txt`` i VSCode for å se innholdet og formateringen. Kjør Python-filen. Det valgfrie argumentet ``encoding`` til funksjonen ``open()`` sier med hvilken encoding filen skal leses. Siden Timemachine-filen er kodert med UTF-8, nevner vi det her. 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 .. _uke_10_oppgave_1: Oppgave 1 ......... I filen ``uke_10_oppg_1.py``, skriv en funksjon som heter ``open_file(filename)`` som åpner en fil med navn ``filename`` og returnerer én streng som inneholder hele filinnholdet. (Denne strengen skal altså inneholde alle newlines og whitespaces som funnets i tekstfilen.) .. note:: Du kan bruke :download:`askeladden.txt` som en test fil, men funksjonen skal fungere med forskjellige filnavn. .. _uke_10_oppgave_2: Oppgave 2 ......... I filen ``uke_10_oppg_2.py``, skriv en funksjon som heter ``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. (Hvor det er radbrytninger i tekstfilen skal det og være det i strengen.) Eksempelkjøring med :download:`askeladden.txt`: .. code-block:: text >>>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 ... ... Eksempel 2 .......... Her får vi en tekstfil med makstemperaturer i løpet av en uke: :download:`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 temperaturene fra filen og lagrer i en liste med tupler. Vi putter temperaturen først i tuplene fordi da kan vi direkte bruke ``sort()`` til å sortere på temperaturen (``sort()`` vil sortere med hensyn til tuplenes første elementer i en liste med tupler). Dette er IO-delen av programmet vårt. Når vi har en liste med tupler sorterer vi den for å enkelt finne høyeste og laveste temperaturen. Dette er dataanalysdelen av programmet vårt. 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. Her er koden: :download:`eksempel_2.py`. Skjønner du hva som skjer? .. literalinclude:: eksempel_2.py .. _uke_10_oppgave_3: Oppgave 3 ......... I filen ``uke_10_oppg_3.py``, skriv en funksjon som heter ``r_w_file(infile, outfile)`` som åpner filen med navn ``infile`` (som er en fil med temperaturer i løpet av en uke, slik som :download:`temperatures.txt`), looper over linjene på filen og lager en ny fil med kun de linjene hvor temperaturen er 23.5 °C eller mer. Denne nye filen skal ha navnet fra ``outfile`` variablen. (Om ingen dag har en temperatur som er minst 23.5 °C så skal ``outfile`` være tom.) Eksempelkjøring med :download:`temperatures.txt`: .. code-block:: text Monday 23.5 Wednesday 24.0 Thursday 23.9 Sunday 23.9 Eksempel 3 .......... 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_3.py`. Kjør koden. Skjønner du hva som skjer? .. literalinclude:: eksempel_3.py .. _uke_10_oppgave_4: Oppgave 4 ......... I filen ``uke_10_oppg_4.py``, skriv en funksjon som heter ``first_letter_last_word(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*. Eksempelkjøring med :download:`askeladden.txt`: .. code-block:: text 'vlf' Eksempler - Exception handling ------------------------------ Du har støtt på exceptions før i denne kursen, men i alle de tilfellene så krasjet programmet. Nå skal vi se på hvordan man kan håndtere exceptions sånn at programmet ikke krasjer. Eksempel 4 .......... 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: .. code-block:: python 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 å bruke ``except Exception`` 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_4.py` og kjør den. Skjønner du forskjellen mellom de ulike måtene å bruke ``except``? .. literalinclude:: eksempel_4.py .. _uke_10_oppgave_5: Oppgave 5 ......... I filen ``uke_10_oppg_5.py``, skriv en funksjon som heter ``first_letters(filename)`` som bruker ``first_letter_last_word(filename)`` fra oppgave 4. 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 `_. Eksempel 5 .......... I denne oppgaven skal brukeren gi heltall (så mange som brukeren vil) og så skal programmet skrive ut summen av alle tallene. Her må vi bruke ``try-except`` for at ikke programmet skal krasje om brukeren angir noe som ikke er et heltall. Det som kan lage en exception i dette tilfellet er om vi forsøker å konvertere en streng som ikke tilsvarer et heltall, til et heltall. Da får vi ``ValueError``. Derfor må vi fånge det med hjelp av ``except ValueError``. Last ned koden her :download:`eksempel_5.py` og kjør den. Skjønner du hva som skjer? .. literalinclude:: eksempel_5.py .. _uke_10_oppgave_6: Oppgave 6 ......... Dette er en enkel funksjon, men den kan kræsje programmet hvis vi blander datatyper: .. code-block:: python def add_together(a, b, c, d): return a + b + c + d I filen ``uke_10_oppg_6.py``, 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(x, y, z, w)``. Hvis noe går galt skal du skrive ut ``Failed with error:`` og så feilmeldingen, til slutt skal funksjonen returnere ``None``. Bruk ``try-except`` format i funksjonen. Eksempelkjøring (i interaktiv modus): .. code-block:: text >>> 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' .. _uke_10_oppgave_7: Oppgave 7 ......... I filen ``uke_10_oppg_7.py``, 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.** Eksempel 6 .......... 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_6.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_6.py Her putter vi ``try`` rundt flere rader kode. Dette betyr at vi har litt mindre å jobbe med om vi får en exception, siden vi da ikke kan bruke noe av koden som er under ``try``. I dette eksemplet betyr det at vi ikke kan returnere tallene som var riktige i filen, uten vi må returnere en tom liste. 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 7 .......... 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_7_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_7_1.py Last ned filen her: :download:`eksempel_7_2.py` som er samme kode som :download:`eksempel_7_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_7_2.py Eksempel 8 .......... Vi kan produsere exceptions manuellt ved å bruke ``raise``. I eksemplet vil vi definere en funksjon som konstruerer nullvektoren av en gitt dimensjon, men dette er bare definert for ikke-negative heltall. Så om vi får et heltall som er negativt bruker vi ``raise ValueError`` til å manuellt produsere et ``ValueError``. I dette tilfellet sender vi også med en melding om hva som er feil: ``raise ValueError("negative dimension")``, men man må ikke sende med en melding, da skriver man bare ``raise ValueError``. Last ned koden her: :download:`eksempel_8_1.py`, og kjør den. Skjønner du hva som skjer? Hvorfor krasjer programmet om vi kjør koden ``print(zero_vector(-1)``? .. literalinclude:: eksempel_8_1.py Vi kan også definere egne feil. I dette eksemplet definerer vi feiltypen ``NoData``. Programmet skal spørre brukeren om et dato eller en temperatur. Om brukeren gir et dato skal programmet skrive ut temperaturen på det datoet. Om brukeren velger en temperatur skal programmet skrive ut alle datoer med den temperaturen. Men om det ikke finnes data som tilsvarer det brukeren spør om produserer vi et feil av typen ``NoData`` som så håndteres og så skriver programmet ut at det ikke finnes tilsvarende data. I dette tilfellet sender vi ikke med en melding når vi bruker ``raise``. Last ned koden her: :download:`eksempel_8_2.py`, og kjør den. Skjønner du hva som skjer? Hvordan kan vi håndtere når det ikke finnes tilsvarende temperaturdata uten å bruke exceptions? Synes du det er bedre å håndtere utilgjengelig temperaturdata med eller uten å bruke exceptions? Hvorfor? .. literalinclude:: eksempel_8_2.py .. _uke_10_oppgave_8: Oppgave 8 ......... 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) I filen ``uke_10_oppg_8.py``, skriv en funksjon som heter ``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 `_. Oppgaver -------- .. note:: Du kan bruke :download:`askeladden.txt` som en test fil, men funksjonene skal fungere med ulike filnavn). ``pytest``-filene lastes ned :download:`uke_10_tests.zip` Oppgave 1 ......... Du finner :ref:`uke_10_oppgave_1` nedenfor Eksempel 1. Oppgave 2 ......... Du finner :ref:`uke_10_oppgave_2` nedenfor Eksempel 1. Oppgave 3 ......... Du finner :ref:`uke_10_oppgave_3` nedenfor Eksempel 2. Oppgave 4 ......... Du finner :ref:`uke_10_oppgave_4` nedenfor Eksempel 3. Oppgave 5 ......... Du finner :ref:`uke_10_oppgave_5` nedenfor Eksempel 4. Oppgave 6 ......... Du finner :ref:`uke_10_oppgave_6` nedenfor Eksempel 5. Oppgave 7 ......... Du finner :ref:`uke_10_oppgave_7` nedenfor Eksempel 5. Oppgave 8 ......... Du finner :ref:`uke_10_oppgave_8` nedenfor Eksempel 8. Oppgave 9 - Submarine Course ............................ Tilpasset fra `Advent of Code 2021, Day 2 `: Gratulerer! Du lærer å styre en ubåt. Ubåten kan ta en rekke kommandoer som ``forward 1``, ``down 2`` eller ``up 3``: - ``forward X`` øker den horisontale posisjonen med ``X`` enheter. - ``down X`` øker dybden med ``X`` enheter. - ``up X`` reduserer dybden med ``X`` enheter. Legg merke til at siden du er på en ubåt, påvirker ned og opp dybden din, og dermed får de det motsatte resultatet av det du kan forvente. Ubåten har allerede en planlagt kurs (din input). Du bør nok finne ut hvor det går. For eksempel: .. code-block:: forward 5 down 5 forward 8 up 3 down 8 forward 2 Din horisontale posisjon og dybde starter begge på ``0``. Trinnene ovenfor vil da endre dem som følger: - ``forward 5`` legger til ``5`` til din horisontale posisjon, totalt ``5``. - ``down 5`` legger til ``5`` til dybden din, noe som resulterer i en verdi på ``5``. - ``forward 8`` legger til ``8`` til din horisontale posisjon, totalt ``13``. - ``forward 2`` legger til ``2`` til din horisontale posisjon, totalt ``15``. Etter å ha fulgt disse instruksjonene vil du ha en horisontal posisjon på ``15`` og en dybde på ``10``. (Å multiplisere disse sammen gir ``150``.) I filen ``uke_10_oppg_9.py``, skriv to funksjonene: 1. ``sub_course_positions(path)`` som tar som input en ``.txt`` kurs-filen og returneres en ``dict`` som inneholder den **horisontale posisjonen** og **dybden** du ville ha etter å ha fulgt den planlagte kursen fra filen :download:`sub-path-full.txt`. Bruk ``forward`` og ``depth`` som nøklene til ``dict`` ditt. Eksempelkjøring med :download:`sub-path-sample.txt` filen: .. code-block:: python >>> sub_course_positions(path) {"forward": ... , "depth": ...} 2. ``sub_course(path)`` som ta som input en ``.txt`` kurs-filen, multipliserer den endelige horisontale posisjonen din med den endelige dybden, og returneres en ``int``. Gjerne bruke din sub_course_positions(path) funksjonen i denne funksjonen. Bruk den funksjonen å beregne resultat fra kurs-filen :download:`sub-path-full.txt`. Eksempelkjøring med :download:`sub-path-sample.txt` filen: .. code-block:: python >>> sub_course(path) 150 Oppgave 10 - Turtle Commands ............................ I filen ``uke_10_oppg_10.py``, skriv to funksjonene. Den første skal heter ``write_turtle_str(file_in)`` som leser inn en liste med instruksjoner fra en tekstfil og omskriver hver linje til en sekvens av skilpaddekommandoer som returneres i en ``string``. Den første linjen må være ``import turtle as t`` og den siste linjen av instruktsjonene må være ``t.done()`` .. note:: Tekstfilen kan inneholde kommandoene ``forward``, ``right``, ``left``, ``up``, og ``down`` som betyr det samme som de tilsvarende turtle-kommandoene. Kommandoene ``up`` og ``down`` har ingenting i neste posisjon i tekstfilen - det betyr at turtle-kommandoen har tomt parentes, f.eks, ``t.up()`` Den andre funksjonen skal hete ``turtle_file_out(turtle_str, file_out)``, og den burde ta inn strengen som den første funksjonen opprettet og skrive den ut til en ny tekstfil. Bruk :download:`turtle-path-1.txt` for å utvikle funksjonen din, men den skal fungere også med alle andre tekstfiler som har denne formateringen. Eksempelkjøring: .. code-block:: python import turtle as t t.forward(50) t.right(90) ... t.down() ... ... t.done()