En funksjon pakker et stykke logikk bak et navn slik at vi kan gjenbruke den. Vi definerer funksjoner med def, sender inn argumenter til parametere og henter resultater med return. Vi ser også på scope (rekkevidden til variabler) og hvordan import gir tilgang til moduler.
Læringsmål
01Definere en funksjon med def, parametre og return, og forklare forskjellen på å definere og å kalle en funksjon
02Skille parameter fra argument og forklare hva en funksjon uten return gir tilbake
03Forklare lokalt og globalt scope og hvorfor lokale variabler isolerer funksjoner fra hverandre
04Importere og bruke funksjoner fra en modul i standardbiblioteket, og spore en kall-stack ved funksjonskall
Etter hvert som programmene vokser, oppdager du at de samme linjene dukker opp igjen og igjen. Du regner ut arealet av en sirkel her, og så igjen lenger nede, og en tredje gang i en løkke. Kopierer du koden hver gang, må du rette tre steder når formelen er feil. Løsningen er å gi arbeidet et navn én gang, og siden bare bruke navnet. Det navngitte arbeidet kaller vi en funksjon.
Du har allerede brukt funksjoner: print(...), len(...) og range(...) er alle funksjoner andre har skrevet. Nå skal du lage dine egne. En funksjon er en gjenbrukbar blokk som tar inn data, gjør noe, og gjerne gir et svar tilbake.
def — å definere en funksjon
Du lager en funksjon med nøkkelordet def, et navn, en parentes med eventuelle inn-verdier, kolon, og en indentert blokk.
Funksjonen defineres først (Python husker den), og kjøres først ved kallet areal(5). Parameteren radius får verdien 5, og return sender arealet tilbake der det lagres i a.
To ting skjer her, og rekkefølgen er viktig. Først definerer vi funksjonen — Python leser den og husker den, men kjører ingenting av blokken ennå. Deretter kaller vi den med areal(5). Først da hopper kjøringen inn i funksjonsblokken, regner ut, og kommer tilbake med svaret.
I overskriften def areal(radius): er radius en parameter: et navn funksjonen bruker innvendig på den verdien den får inn. Når vi kaller areal(5), er tallet 5 et argument: den faktiske verdien vi sender inn. Parameteren er navnet i oppskriften; argumentet er ingrediensen du faktisk putter oppi. En funksjon kan ha flere parametre, skilt med komma, og da må kallet sende like mange argumenter i samme rekkefølge.
return — å sende et svar tilbake
Nøkkelordet return avslutter funksjonen og sender en verdi tilbake til der den ble kalt. Verdien kan brukes videre: lagres i en variabel, skrives ut, eller settes inn i et nytt uttrykk.
python
defstorst(a, b):
ifa > b:
returnareturnbvinner = storst(8, 3) # vinner blir 8print(storst(2, 9)) # skriver 9
Idet en return kjører, stopper funksjonen — koden etter return i samme gren kjøres ikke. Det er nyttig: i storst betyr return a at vi aldri når return b når a er størst.
Skriver du en funksjon uten return, gir den automatisk tilbake spesialverdien None. Det er helt greit for funksjoner som bare gjør noe — for eksempel skriver til skjerm — uten å regne ut et svar. Men en vanlig nybegynnerfeil er å print-e inne i funksjonen når man egentlig trengte å return-ere: da ser du tallet på skjermen, men kan ikke regne videre med det. Faustregelen er: bruk return når noen som kaller funksjonen trenger svaret videre, og print bare når selve poenget er å vise noe for brukeren.
Funksjoner kan også ha standardverdier på parametre, slik at et argument blir valgfritt. Skriver du def hils(navn, tegn="!"):, kan du kalle hils("Ada") og få tegn satt til "!" automatisk, eller hils("Ada", "?") for å overstyre. Standardverdier gjør funksjoner fleksible uten at hvert kall må fylle ut alt.
Hva som skjer ved et kall
Det hjelper å se selve flyten ved et funksjonskall: kjøringen forlater stedet den var, hopper inn i funksjonen, kjører blokken, og returnerer til akkurat det stedet med svaret i hånd.
FIGFlyten ved et funksjonskall
Bak kulissene holder Python orden på dette med en kall-stack. Hver gang en funksjon kalles, legges en ny frame på toppen av stacken: en liten arbeidsbenk med funksjonens egne lokale variabler. Når funksjonen returnerer, fjernes frame-en, og vi er tilbake i den som kalte.
FIGKall-stack: areal-frame oppå main
scope — hvor et navn lever
Dette leder til et begrep mange synes er vanskelig: scope, altså hvilket område i koden et variabelnavn er synlig i. En variabel som lages inne i en funksjon, er lokal: den finnes bare mens funksjonen kjører, og forsvinner når funksjonen returnerer. Utenfor funksjonen finnes den ikke.
python
defregn():
svar = 42# lokal — lever bare her innereturnsvarregn()
print(svar) # NameError: 'svar' er ikke definert
Dette føles strengt, men er en velsignelse. Fordi lokale variabler er isolerte, kan to funksjoner trygt bruke samme navn i eller total uten å trampe på hverandre. Du kan lese og skrive en funksjon uten å bekymre deg for hva resten av programmet kaller variablene sine. Hver gang funksjonen kalles, lages dessuten et helt friskt sett lokale variabler — derfor kan en funksjon trygt kalle seg selv, slik vi skal se når vi kommer til rekursjon.
Parametre teller også som lokale variabler. Endrer du radius inne i areal, påvirker det bare funksjonens egen kopi, ikke noen variabel utenfor. (For enkle tall og strenger gjelder dette uten forbehold; for lister og andre samlinger er bildet litt mer nyansert, men det venter vi med.)
Variabler som lages utenfor alle funksjoner, er globale og kan leses fra inni en funksjon. Å endre en global variabel innenfra krever ekstra arbeid og frarådes som regel — det gjør koden vanskeligere å følge. Et bedre mønster er å sende verdien inn som argument og få det endrede svaret ut via return. Da er det tydelig hva som går inn og hva som kommer ut, og funksjonen blir lett å teste.
import — å bruke andres funksjoner
Du trenger ikke skrive alt selv. Python kommer med et stort standardbibliotek av ferdige funksjoner, samlet i moduler. En modul er ganske enkelt en fil med funksjoner og verdier du kan låne. Du henter den inn med import.
Etter import math når du innholdet med prefikset math.. Prefikset sier hvor navnet kommer fra, så math.pi ikke kolliderer med en egen variabel pi du måtte ha. Vil du bare ha enkeltdeler, kan du skrive from math import sqrt, pi, og da bruker du sqrt og pi uten prefiks. Modulen random gir tilfeldige tall, statistics gir gjennomsnitt og median, og det finnes hundrevis flere. Å vite at en modul finnes er ofte halve jobben — resten er å slå opp hva den inneholder.
python
importrandomterning = random.randint(1, 6) # et tilfeldig tall mellom 1 og 6print("du kastet", terning)
Importer holder du øverst i fila, før den øvrige koden, slik at en leser umiddelbart ser hvilke moduler programmet hviler på. Et nyttig poeng: dine egne .py-filer er også moduler. Skriver du en samling funksjoner i verktoy.py, kan en annen fil i samme mappe skrive import verktoy og bruke verktoy.areal(...). Slik vokser et program fra én fil til flere, der hver fil samler funksjoner som hører sammen — selve grunntanken bak å dele opp større kode.
Hvorfor funksjoner gjør koden bedre
Funksjoner gir tre gevinster på én gang. De fjerner gjentakelse: skriv logikken én gang, kall den mange. De gir navn til ideer, slik at areal(5) forteller leseren hva som skjer langt klarere enn fire linjer formel. Og de innkapsler: det som skjer inni en funksjon, holder seg der, så du kan endre innmaten uten å røre resten av programmet, så lenge inn- og ut-verdiene er de samme. Denne siste gevinsten er grunnen til at funksjoner skalerer: et stort program er håndterbart nettopp fordi du kan tenke på én funksjon om gangen, stole på at de andre gjør jobben sin, og slippe å holde hele programmet i hodet samtidig. En tommelfingerregel er at hver funksjon bør gjøre én ting, og at navnet bør si hvilken. Klarer du ikke å sette et kort, dekkende navn, gjør funksjonen sannsynligvis for mye, og bør deles i to.
Mine notater
Skriv egne notater for dette kapittelet. De lagres automatisk og dukker opp i «Mine notater»-oversikten paa fag-siden.