Uke 5 — functions / def / return ================================ Introduksjon ------------ Denne uken skal vi jobbe med funksjoner i Python. La oss først se på de innebygde funksjonene i Python. Du finner en liste i `Pythons dokumentasjon `_. Det er vel verdt å bli kjent med dokumentasjonen. Hvis du ikke kan en funksjon fra før, kan du søke den opp her og finne ut hva den gjør. Det er ikke nødvendig å lese alt dette nå, men å lese på de funksjonene du har bruk for. For nå kan det være lurt å lese på funksjonene ``len()``, ``max()``, ``min()``, ``abs()``, ``int()``, ``str()``, ``float()``, ``round()`` og ``input()``. .. note:: Denne uken vil tilbakemeldingene være litt forskjellig fra tidligere ukene. I stedet for I/O tester på Codegrade-nettstedet, vil vi bruke ``pytest``-tester som du kan kjøre selv på datamaskinen din før du laster opp til codegrade, eller kjøre som vanlig og lese resultater fra testene på Codegrade-nettstedet. Om du vil kjøre testene selv før du laster opp, må du gjør det følgende: 1. Installere ``pytest``: Skriv kommandoen ``python3 -m pip install -U pytest`` i terminalen (om det ikke fungerer, kan du også prøve å kjøre dette skriptet :download:`install.py`). Du kan lese mer om ``pytest``-modulen i `pytest dokumentasjon `_. 2. Last ned testene i zip-filen :download:`uke_05_tests.zip`. Lagre denne mappen i ukens oppgave og pakk ut. En bra struktur å bruke er én hovedmappe som heter ``uke_05`` som inneholder all oppgavenefilene sammen med den ``test``-mappen som inneholder alle tilsvarende testfiler. 3. Du kan kjøre alle testene samtidig fra hovedmappen din (f.eks. ``uke_05``) med kommando i terminalen: ``python3 -m pytest`` eller du kan kjøre hver test individuelt, f.eks. ``test_1.py``, med følgende kommando: ``python3 -m pytest test/test_1.py`` 4. Når vi kjører ``pytest`` vil du få litt mer informasjon enten med en gang i terminalen din eller på Codegrade hvis du velger å sjekke koden din på denne måten. Testene forteller deg hvilke testtilfeller som består, og hvilke som feiler. Ved feil vil testen gi deg litt informasjon om hvordan og hvor i koden feilen ligger. Du kan bruke denne informasjonen til å finne problemer i og forbedre koden din. Eksempler --------- Eksempel 1 .......... Funksjoner er veldig nyttige i programmering. Blant annet lar de deg gjenbruke kodeblokker for å redusere repetisjon og feil du kan gjøre mens du programmerer, fordi funksjoner gjør at man kan kjøre samme blokk med kode flere ganger, uten å måtte skrive den samme koden flere ganger. Her er et eksempel på kode hvor vi bruker funksjoner. I koden ser du at vi ikke nødvendigvis må kalle funksjonene i samme rekkefølge som de er definert. Som *argument* kan en funksjon ta ingen verdier, én eller flere verdier, og verdier med blandede typer, f.eks. ``int`` og ``str``, som vi ser nedenfor: Last ned filen her: :download:`eksempel_1.py`. .. literalinclude:: eksempel_1.py Eksempel 2 .......... I dette eksempelet skal vi bruke flere funksjoner sammen for å bestille kaffe og kjeks. Først en funksjon for å si hallo, og så en ny funksjon for å bestille mat. Legg merke til at den tredje funksjonen ``make_order`` kaller de andre funksjonene med ulike argumenter, og bruker resultatene. Kan du tegne flyten til programmet, dvs. hvilke linjer blir utført i hvilken rekkefølge når programmet kjører? Last ned filen her: :download:`eksempel_2a.py`. .. literalinclude:: eksempel_2a.py Skjønner du hvordan resultatet fra de andre funskjonene gjenbrukes? Eksempelene her bruker også "docstrings" (documentation strings). Dette er teksten som står inne i trippel-quotes ``"""`` helt i begynnelsen av en funksjon. Docstrings forklarer hva funksjonen gjør og hvordan den kan brukes av andre programmer. Prøv for eksempel å holde musen over funksjonsnavnene i VScode. Her er et annet eksempel der vi bruker en funksjon som argument til en annen. :download:`eksempel_2b.py`. .. literalinclude:: eksempel_2b.py Hvordan brukes den første funskjonen i den andre funksjonen? Tegn programmflytet. Eksempel 3 .......... Funksjoner kan også brukes for å gjøre matematiske beregninger. Mange ganger vil man gjøre samme beregning flere ganger. Da er det lurt å plassere koden for beregningen i en funksjon som man siden kan kalle flere ganger. Om man vil gjøre flere ulike beregninger etter hverandre kan man dessuten gjøre det. Her er et eksempel på kode hvor vi bruker funksjoner for å regne ut lengden til hypotenusen i en rettvinklet trekant og gjennomsnittet av tre tall. Last ned filen her: :download:`eksempel_3.py`. .. literalinclude:: eksempel_3.py Kjør koden. Ble outputen hva du hadde ventet deg? Kan du lage noen funksjoner til, for å unngå repetisjonen i print-linjene? Eksempel 4 .......... I dette eksempelet bruker vi en funksjon for en beregning og en funksjon for å lage en streng. Vi beregner og printer andregradspolynomer. Last ned filen her: :download:`eksempel_4.py`. .. literalinclude:: eksempel_4.py Her brukes ``return`` i ``poly_string()``. Prøv å endre koden slik at ``poly_string()`` printer strengen, i steden for å bruke ``return``. Hva skjer? Eksempel 5 .......... Det er mulig å definere en funksjon i Python uten å bruke ``return``. Da returnerer Python verdien ``None`` i bakgrunnen. Husk at om du vil gi verdien fra en funksjon til en annen funksjon må du bruke ``return``. Last ned filen her: :download:`eksempel_5.py`. .. literalinclude:: eksempel_5.py Hva er forskjellen på x og y? Hvis vi skal bruke ``50`` igjen senere i programmet, burde vi se på ``x`` eller ``y``? Eksempel 6 .......... Funksjoner uten return (de har return-verdien ``None``) brukes ofte for å gruppere instrukser som hører sammen, og skal gjenbrukes flere ganger. Nå kan vi endre på ting på ett sted og få den endringen med på alle steder der funksjonen er brukt. Om vi hadde kopiert instruksjonene flere ganger uten å bruke en funksjon måtte vi fikset hver enkel kopi. Last ned filen her: :download:`eksempel_6.py`. .. literalinclude:: eksempel_6.py Kjør koden. Hvorfor blir ikke ``ascii_cube()`` skrevet ut? .. note:: Noen steder i koden er det brukt en ekstra ``\``. Se for eksempel på ``ascii_castle()``. Her står det ``\'`` noen steder, men når vi kjører programmet printes ikke ``\``. Dette er fordi ``\`` er en så kalt 'escape character'. Vi kan bruke ``\`` før tegn som ellers ville ha noen annen betydning i en streng. For eksempel, om vi ikke hadde brukt ``\`` før ``'`` ville Python trodd at strengen var slutt ved ``'`` og vi hadde fått en feilmelding (prøv gjerne). Men når vi putter ``\`` før ``'`` sier vi til Python at vi ikke mener at strengen er slutt her, uten at Python skal printe tegnet ``'``. Siden ``\`` er et spesielt tegn på denne måten må vi bruke ``\`` før ``\`` om vi vil at Python skal lese strengen som tegnet ``\`` og ikke som en escape character. Det er derfor det står ``\\`` på alle steder hvor vi vil printe ``\``. Du kan lese mer om escape characters `her `_, og her er en liste med eksempler på `alle escape characters `_ i Python 3. Eksempel 7 .......... .. image:: turtle-out.png :scale: 30 % :alt: Turtle module output for ``trefoil()`` :align: right Her er eksempler på de mange måtene vi kan bruke funksjoner til å tegne enkle eller kompleksere former i ``turtle``-modulen. **Eksperimenter med å justere verdiene til de forskjellige variablene** for å se hvordan dette endrer tegningen din for hvilken funksjon du velger å kalle. Last ned filen her: :download:`eksempel_turtle.py`. .. literalinclude:: eksempel_turtle.py Eksempel 8 .......... Her er et eksempel på hva som skjer om man ikke bruker ``return`` i funksjoner. Hva om vi vil lagre resultatet i en variabel? For eksempel, hvis vi vil huske navnet fra tidligere funksjon ``hello`` kan vi lagre resultatet i ``myName``. I eksempelet er det to funksjoner: ``hello1`` og ``hello2``. ``hello1`` bruker print() mens, ``hello2`` bruker return i slutten av funskjonen. Last ned filen her: :download:`eksempel_8.py`. .. literalinclude:: eksempel_8.py Kjør koden. Hva er forskjellen på ``hello1`` og ``hello2``? Hva lagres i ``myName``? Det er ikke vanlig å ha med ``print()`` i en funksjon som skal regne ut noe. Returner verdien eller en streng i steden for. Da kan brukeren til funksjonen bestemme om det skal printes noe eller om verdien skal gjenbrukes på en annen måte. Eksempel 9 .......... Last ned filen her: :download:`eksempel_9.py`. .. literalinclude:: eksempel_9.py :linenos: Når du prøver å kjøre koden får du en feilmelding. - Se på det vanlige outputet før feilmeldingen: hvorfor ser det slik ut? - Hvorfor får du error når du kjører koden? - Hvorfor blir ikke den siste raden skrevet ut? - Endre koden slik at den kjører. - Hvorfor fikk du ingen feilmelding fra ``den_er_feil()``-funksjonen? Oppgaver -------- I disse oppgavene skal du lage og bruke funksjoner. .. warning:: Ikke bruk ``input()`` i koden din, fordi da vil autotestene ikke kjøre. Oppgave 1 ......... .. note:: Oppgave 1 består av **fem** deler. Skriv alle funksjoner til de fem delene (a-e) i én felles fil: ``uke_05_oppg_1.py`` (a) I filen ``uke_05_oppg_1.py``, skriv en funksjon .. code-block:: python def multiply_5_plus_2(my_number): # din kode her Funksjonen skal * ta et argument (av typen ``int``), * multiplisere dette med `5` og legg til `2`, * returnere resultatet. Det er ikke nødvendig å printe ut resultatet. For eksempel skal ``multiply_5_plus_2(4)`` returnere verdien ``22``. (b) Nå skal vi prøve et likt problem som oppgave 1, men denne tiden med typen ``str``. Skriv en funksjon .. code-block:: python def make_excited(text): # din kode her Funksjonen skal * ta et argument (av typen ``str``), * legge til en ``"!"`` til slutten av ordet eller frasen, * returnere resultatet. *Ikke ta input fra brukeren*, bare lag funksjonen. Om du vil sjekke den, kan du f.eks printe den med ulike argumenter. ``make_excited("I love programming")`` skal returnere strengen ``"I love programming!"`` (c) Skriv en funksjon som heter .. code-block:: python def name_age(name, gender, age): # din kode her * Funksjonen skal ta tre argumenter: (1) navnet til en person, (2) personens kjønn og (3) personens alder. Navnet og kjønn er en ``str`` og alderen er ``int``. * Funksjonen skal returnere en streng med setningen ``"... er ... og er ... år gammel."`` For eksempel skal ``name_age("Ola", "kvinne", 7)`` returnere strengen ``"Ola er kvinne og er 7 år gammel."`` (d) Skriv en funksjon som heter ``my_math()``. Funksjonen skal ta to argumenter :math:`x` og :math:`y` og skal returnere :math:`x^2 + 2y`. .. code-block:: python def my_math(x, y): # din kode her For eksempel skal ``my_math(2, 2)`` returnere verdien ``8.`` (e) I denne delen skal vi bruke med ``min()`` og ``max()`` for å identifisere den **yngre** av to mennesker. Skriv en funksjon .. code-block:: python def hvem_yngre(person1, age1, person2, age2): # din kode her som tar som input fire argumenter: navnet på første personen (``string``), alderen på første personen (``int``), navnet på andre personen (``string``), alderen på andre personen (``int``). Funksjonen bestemmer hvem som er yngre og returnerer en setning som angir navn og alder på den yngre personen, vist i det følgende eksempelet: ``hvem_yngst("Ola", 43, "Katrine", 23)`` skal returnere strengen ``"Katrine er 23 år og er yngre."`` Vi ser bort fra tilfellet der begge har lik alder. Oppgave 2 ......... I denne oppgaven skal vi dra tilbake til uke_02 der vi laget en ramme rundt et haiku. Denne gangen vil vi skrive dette som en funksjon, og vi skal **ikke** bruke ``print()`` i funksjonen. I filen ``uke_05_oppg_2.py``, skriv en funksjon som heter ``draw_haiku_frame()`` som tar de tre radene i en haiku som tre argumenter og returnerer en streng med hele haikuen med høyrejustering og med en ramme av "@" runt. For eksempel, om vi printer resultatet av ``draw_haiku_frame("What a pleasure to", "Right justify a haiku", "As an exercise")`` skal vi få følgende output: .. code-block:: text @@@@@@@@@@@@@@@@@@@@@@@@@ @ What a pleasure to @ @ Right justify a haiku @ @ As an exercise @ @@@@@@@@@@@@@@@@@@@@@@@@@ .. note:: **TIPPS:** Som vi gjorde sist, kan du bruke ``len()`` og ``max()`` til å finne lengden av den lengste raden. Så printer du hver rad med så mange mellomrom før raden som det er forskjell mellom den raden og den lengste raden i haikuen, husk å printe en rad med ``@`` før og etter haikuen og husk å printe ``@`` i starten og slutten av hver rad med tekst. Hvor mange tegn lengre må toppen og bunnen av rammen være enn den lengste raden i haikuen? Oppgave 3 - pH-verdier ...................... Begrepet :math:`pH` er en forkortelse for *potensialet til hydrogen*. pH er en måleenhet som representerer konsentrasjonen av hydrogenioner i en løsning. - En løsning med hydr.-konsentrasjon :math:`0.1\frac{\mathrm{mol}}{\mathrm{L}}` har pH-verdien :math:`1`, - En løsning med hydr.-konsentrasjon :math:`0.01\frac{\mathrm{mol}}{\mathrm{L}}` har pH-verdien :math:`2`, - En løsning med hydr.-konsentrasjon :math:`0.001\frac{\mathrm{mol}}{\mathrm{L}}` har pH-verdien :math:`3`, osv... Om vi kaller konsentrasjonen for :math:`[H^+]`, beregnes pH-verdien med følgende ligning: .. math:: pH = -log_{10}\,[H^+] pH-skalaen beskriver surheten til løsningen: sur, nøytral eller basisk. En løsning med pH mindre enn ``7`` er en syre, nøyaktig ``7`` er en nøytral løsning og over ``7`` er en base. I filen ``uke_05_oppg_3.py`` skal du definere følgende funksjonene: ``pH_from_conc()`` skal ta som input et tall som er konsentrasjonen av hydrogenioner i en løsning (vi antar at enhet er :math:`\frac{\mathrm{mol}}{\mathrm{L}}`) og skal returnere pH til løsningen som ``float``. For eksempel, en :math:`0.20\frac{\mathrm{mol}}{\mathrm{L}}` HCl-løsning har en pH av ``0.6989``. .. code-block:: python from math import log10 # du må inkludere denne linjen for å importere pythons-loggfunksjonen def pH_from_conc(conc): # din kode her Den andre funksjonen skal hete ``find_acidity()``, og bør ta som input en konsentrasjons-verdi og returnere dens surhet som ``string``: enten ``acidic``, ``neutral`` eller ``basic``. **TIPS**: Gjenbruk din første funksjon i denne funksjonen. .. code-block:: python def find_acidity(conc): # din kode her Oppgave 4 ......... **Den Hydrostatiske Balansen - Del I** Trykket (P) i atmosfæren og i det indre hav kan i en første tilnærming beskrives som hydrostatisk. Trykket (i enheten Pascal), som en partikkel plassert på en dybde :math:`z` i havet er utsatt for gis av følgende formel: .. math:: P = \rho \cdot g \cdot z hvor :math:`\rho` er vanndensiteten (med enheten :math:`\text{kg} \cdot \text{m}^{-3}`), :math:`g` er tyngdekraftens akselerasjon (med enheten :math:`\text{m} \cdot \text{s}^{-2}`) og :math:`z` er partikkelens dybde (med enheten :math:`\text{m}`). SI-enheten for trykk er Pascal :math:`[\text{Pa}]`, men i meteorologi og oceangrafi blir dokk trykk vanligvis presentert i enheten decibar :math:`[\text{dbar}]`. Disse to henger sammen ifølge formelen: :math:`1\,\text{Pa} = 10^{-4}\,\text{dbar}`. I filen ``uke_05_oppg_4.py`` skriv en funksjon som heter ``hyd_pres()`` og som beregner trykket på en gitt dybde i enheten :math:`\text{dbar}`. Argumentene til funksjonen skal være: en densitetsverdi (:math:`\rho`), tyngdekraftens akselerasjon (:math:`g`) og dybden (:math:`z`). Se koden nedenfor for en oversikt over hvordan du kan nærme deg dette problemet. .. code-block:: python def hyd_pres(density, g, z): # din kode her # første, beregne trykk i Pascal (Pa) # deretter, konvertere trykk i Pascal (Pa) til trykket i dbar return # trykket i dbar For eksempel, om vi bruker densiteten :math:`\rho = 1025\,\text{kg} \cdot \text{m}^{-3}` og tyngdeakselerasjonen :math:`g = 10\,\text{m} \cdot \text{s}^{-2}` så er trykket :math:`102.5\, \text{dbar}` på dybde :math:`100\, \text{m}`. Det betyr altså at ``hyd_pres(1025,10,100)`` skal returnere verdien ``102.5``. Pytest-filer ------------ Pytest-filene vi bruker på Codegrade: :download:`uke_05_tests.zip`