2.2 Funksjonssamarbeid og scope
Hvordan funksjoner samarbeider, og reglene for **scope**: en lokal variabel i en funksjon endrer ikke en variabel utenfor — grunnlaget for scope-spørsmålene i Oppgave 2.
i Oppgave 2, flervalgsoppgaven om objekter. Den oppgaven er i 6 av 6 moderne eksamenssett (100 %) og er verdt 8–9 poeng, og boka kaller sjangeren
sjanger B — flervalg om objekter: hvilke navn peker på hva, og hva skjer da?
Selve spørsmålet er alltid det samme i bunn: en tilordning inne i en funksjon lager en ny, lokal variabel — den endrer ikke en variabel utenfor. Det er feilkode #12, scope-forvirring.
Kapitlet er kort, men det er en av bokas viktigste forberedelser. Nøyaktig den samme mekanismen er årsaken til self-fella i
kap. 5.1, som er den enkeltfeilen sensor planter oftest i hele faget. Forstår du scope her, får du self-fella nesten gratis.
Dette må sitte — bokas høyeste av tre nivåer, som forkunnskap til Oppgave 2.
Forkunnskaper — sist du var her
Kap. 2.1 — Funksjoner: def, parametere og return.
De tre tingene du trenger derfra:
- at en funksjon defineres én gang og kalles så mange ganger du vil,
- at parameteren er navnet i definisjonen og argumentet er verdien som
sendes inn,
- at return avslutter funksjonen og gir en verdi tilbake — og at en funksjon
uten return gir None.
Kort oppfrisket:
def dobbel(tall):
return tall * 2
svar = dobbel(21)
print(svar)Utskrift:
42Spørsmålet i dette kapitlet er hva som skjer med navnene: hva heter tall utenfor funksjonen, og hva skjer om du tilordner et navn som allerede finnes utenfor?
Løkke 1 — Lokale variabler (~12 min)
Et program kan ha mange variabler med samme navn uten at de kolliderer, fordi hver funksjon har sitt eget lille rom av navn.
Det rommet heter et scope, og reglene for det er få — men de er kilden til et av de mest testede flervalgsspørsmålene på eksamen.
Området i programmet der et variabelnavn er synlig og kan brukes.
I IN1000 trenger du bare to nivåer:
- Lokalt scope — inne i en funksjon. Alt som lages der, finnes bare mens
funksjonen kjører.
- Globalt scope — utenfor alle funksjoner, altså i hovedprogrammet.
Regelen som avgjør alle spørsmålene: en funksjon kan lese navn fra det globale scopet, men en tilordning inne i funksjonen lager alltid et nytt, lokalt navn.
Ordet uttales «skoup» og betyr rett og slett «virkeområde». Du møter det i oppgavetekster på eksamen, så det er verdt å kjenne igjen.
En variabel som lages ved en tilordning inne i en funksjon. Den finnes bare mens funksjonen kjører, og forsvinner når den returnerer:
def regn():
mellomsvar = 10 * 3 return mellomsvar
print(regn())Utskrift:
30Prøver du å bruke mellomsvar etter kallet, får du `NameError: name
'mellomsvar' is not defined` — navnet finnes ikke lenger.
Det er en fordel, ikke en begrensning: derfor kan du bruke i, total ogantall i hver eneste funksjon uten at de kolliderer med hverandre.
En parameter er en helt vanlig lokal variabel. Den får verdien sin fra argumentet, og den forsvinner når funksjonen returnerer:
def dobbel(tall):
tall = tall * 2 return tall
x = 5 print(dobbel(x), x)Utskrift:
10 5Legg merke til at x er uendret. Funksjonen fikk verdien 5 inn i sin egen lokale tall, og endret den lokale kopien.
Dette gjelder for tall, tekst og boolske verdier — altså alt du har møtt så langt. I kap. 5.4 møter du datatyper der bildet er mer sammensatt, og det er nettopp det Oppgave 2 tester.
Dette er kapitlets kjerne, og den mest testede scope-feilen i arkivet.
x = 5
def f():
x = 10 return x
print(f(), x)Utskrift:
10 5Funksjonen returnerte 10, men x i hovedprogrammet er fortsatt 5.
Hva skjedde: linja x = 10 inne i funksjonen laget en ny, lokal x. Den
globale x ble aldri rørt. De to navnene ser like ut, men er to forskjellige variabler.
Kjenn igjen fella på formen. Ser du en tilordning til et navn som også finnes
utenfor, er svaret nesten alltid at utsiden er uendret.
Merk deg denne mekanismen godt. Den er nøyaktig den samme som gjør at x = 5 i en konstruktør ikke lagrer noe i objektet, mens self._x = 5 gjør det — og det er self-fella, feilkode #1, som du møter i
kap. 5.1.
Programmet har antall fire steder. Hvilke av dem er samme variabel, og hva skriver programmet ut?
antall = 100
def tell_opp(antall):
antall = antall + 1 return antall
def vis():
print("inne i vis:", antall)
print(tell_opp(5))
vis()
print(antall)Gå gjennom navnene ett scope om gangen.
| Sted | Hvilken antall? |
|---|---|
| Linje 1 | den globale — verdien 100 |
Parameteren i tell_opp | en lokal, får verdien 5 fra argumentet |
Tilordningen inne i tell_opp | samme lokale, blir 6 |
Inne i vis | ingen lokal finnes, så den leser den globale |
Utskrift:
6 inne i vis: 100
100Tre poenger:-
tell_opp(5) returnerer 6 og lot den globale være i fred.-
vis leser den globale uten problemer, fordi den ikke tilordner noe med det navnet. En funksjon kan altså se ut i det globale scopet.
- Etter alt sammen er den globale fortsatt 100.
Slik ville sensor sett på det: dette er nøyaktig formen på et
flervalgsspørsmål i Oppgave 2. Det du blir bedt om, er å avgjøre om utsiden ble endret — og svaret er nesten alltid nei.
(Innstegsoppgave — ren gjengivelse.) Se på dette programmet:
poeng = 10
def endre():
poeng = 99 return poenga) Hva returnerer kallet endre()?
b) Hvilken verdi har poeng i hovedprogrammet etter kallet?
c) Hvor mange variabler ved navn poeng finnes det i programmet?
Løkke 2 — Å lese ut, og hvorfor return er svaret (~12 min)
En funksjon kan lese en global variabel den ikke selv tilordner. Det høres praktisk ut, og av og til er det det — men det er også en felle du skal styre unna på eksamen.
En variabel som er laget utenfor alle funksjoner, i selve hovedprogrammet.
Den kan leses fra innsiden av en funksjon:
mva = 0.25
def med_mva(pris):
return pris + pris * mva
print(med_mva(100))Utskrift:
125.0Funksjonen fant mva i det globale scopet fordi den ikke har noen lokal variabel med det navnet.
Det virker — men det gjør funksjonen avhengig av noe som står langt unna. Endrer noen mva et helt annet sted i programmet, endres oppførselen til funksjonen uten at kallet ser annerledes ut.
Tre grunner, i den rekkefølgen de betyr noe på eksamen:
1. Funksjonen blir umulig å lese alene. Du må lete i hele programmet for å
vite hva den gjør.
2. Den blir umulig å gjenbruke. En funksjon som henter data fra utsiden, kan
ikke kalles med andre data.
3. Den blir umulig å teste. Du kan ikke gi den nye verdier uten å endre
programmet rundt.
Regelen i faget: alt en funksjon trenger, kommer inn som parameter, og
alt den produserer, går ut som returverdi. Det er den formen sensor forventer, og den formen alle fasitene har.
I kap. 5.1 får du den ordentlige løsningen på «data som skal leve videre»: å legge dem i et objekt i stedet for i en global variabel.
Bokas fjerde kodemønster, og den korteste regelen i hele boka:
Alt en funksjon trenger, tar den inn som parameter. Alt den gir fra seg, går ut
gjennom return.
def med_mva(pris, sats):
return pris + pris * sats
print(med_mva(100, 0.25))
print(med_mva(100, 0.15))Utskrift:
125.0
115.0Sammenlign med versjonen over, som hentet satsen fra en global variabel. Denne kan kalles med hvilken sats som helst, den kan leses alene, og den gjør nøyaktig det navnet sier.
Mønsteret er selve grunnformen for hver eneste funksjon i Oppgave 3 og hver metode i Oppgave 4.
En student har skrevet en funksjon som teller hvor mange tegn i en tekst som er lik et bestemt tegn — men både teksten og tegnet er globale variabler. Vis hvorfor det er upraktisk, og skriv den om.
tekst = "bibliotek" sok = "b"
def tell():
antall = 0 for t in tekst:
if t == sok:
antall = antall + 1 return antall
print(tell())Koden over kjører, og gir riktig svar:
Utskrift:
2Problemet vises først når du vil telle noe annet. Funksjonen tar ingen argumenter, så det eneste du kan gjøre, er å endre de globale variablene før hvert kall — og da må hovedprogrammet vite hvilke navn funksjonen bruker innvendig.
Slik skrives den om:
def tell(tekst, sok):
antall = 0 for t in tekst:
if t == sok:
antall = antall + 1 return antall
print(tell("bibliotek", "b"))
print(tell("bibliotek", "i"))
print(tell("kantine", "n"))Utskrift:
2
2
2Nå kan den kalles med hva som helst, og kroppen er ord for ord den samme.
Slik ville sensor sett på det: en funksjon som henter data fra globale
variabler i stedet for å ta dem inn som parametere, får sjelden full uttelling — selv om den gir riktig svar for det ene tilfellet oppgaven nevner. Det er
gjenbrukbarheten som er poenget med å skrive en funksjon i det hele tatt.
Hva skriver programmet ut?
n = 3
def les():
return n * 2
def skriv_over():
n = 100 return n
print(les())
print(skriv_over())
print(les())
print(n)En funksjon skal regne ut hvor mye et beløp blir med et påslag. Denne versjonen bruker en global variabel:
sats = 0.10
def med_paaslag(belop):
return belop + belop * satsa) Skriv om funksjonen så den tar satsen inn som parameter.
b) Vis to kall med ulik sats.
c) Nevn én konkret grunn til at den omskrevne versjonen er bedre.
Løkke 3 — Scope i sporingsoppgaver (~10 min)
Nå er reglene på plass. Det som gjenstår, er å bli rask på å bruke dem i en sporingsoppgave — for det er nettopp den formen Oppgave 2 har.
Når en funksjon lager en lokal variabel med samme navn som en global, sier vi at den skygger den globale: inne i funksjonen er det bare den lokale som er synlig.
farge = "rod"
def vis():
farge = "bla" print("inne:", farge)
vis()
print("ute:", farge)Utskrift:
inne: bla ute: rodSkyggingen varer bare mens funksjonen kjører. Etterpå er den globale like uberørt som før.
Merk at skyggingen gjelder for hele funksjonen, ikke bare fra tilordningen og nedover — prøver du å lese navnet på en linje før tilordningen i samme funksjon, får du en feilmelding i stedet for den globale verdien.
Den ene regelen du trenger for å svare på alle scope-spørsmål:
| Hva funksjonen gjør | Hvilken variabel treffes |
|---|---|
| bare leser navnet | den globale, hvis ingen lokal finnes |
tilordner navnet (med =) | en ny lokal, alltid |
Det finnes en måte å endre en global variabel fra innsiden på, men den brukes ikke i dette faget og ikke i fasitene — riktig løsning er alltid
return.Sjekklista i en sporingsoppgave er derfor kort: let etter likhetstegn inne i funksjonen. Finner du ett, er navnet lokalt.
Sender du et tall, en tekst eller en boolsk verdi inn i en funksjon, kan funksjonen ikke endre variabelen du sendte inn:
def nullstill(verdi):
verdi = 0 return verdi
poeng = 42 nullstill(poeng)
print(poeng)Utskrift:
42Kallet gjorde ingenting synlig utenfra, fordi returverdien ble kastet og parameteren var lokal.
Legg merke til at dette er en stille feil: programmet krasjer ikke, det gjør bare ingenting. Riktig form er poeng = nullstill(poeng).
Bildet blir mer sammensatt for datatypene i kap. 3.1 og
kap. 5.4, og det er nettopp den forskjellen Oppgave 2 er bygget rundt.
Dette er på formen et flervalgsspørsmål i Oppgave 2 har. Hva skriver programmet ut?
teller = 0
def opp():
teller = 1 return teller
def opp_riktig(teller):
return teller + 1
print(opp())
print(teller)
teller = opp_riktig(teller)
print(teller)Tre steg, og forskjellen mellom dem er hele poenget.
| Steg | Hva som skjer | Global teller etterpå |
|---|---|---|
opp() | lager en lokal teller lik 1 og returnerer den | 0 — uendret |
print(teller) | leser den globale | 0 |
teller = opp_riktig(teller) | sender verdien inn, tar returverdien imot | 1 |
Utskrift:
1
0
1Legg merke til den avgjørende linja. opp_riktig endrer heller ikke noe avseg selv — den bare returnerer en ny verdi. Det som gjør at den globale oppdateres, er at hovedprogrammet tar imot returverdien med
teller = ....
Det er hele oppskriften: parameter inn, returverdi ut, og ta imot svaret.
Slik ville sensor sett på det: kandidater som svarer at opp() setter den
globale til 1, har gått i felle #12. Kandidater som ser at opp() ikke endrer noe, men glemmer at siste linje faktisk gjør det, har forstått halve mekanismen — og får uttelling for det.
Hva skriver programmet ut?
navn = "Ada"
def endre(navn):
navn = navn + "!" return navn
print(endre(navn))
print(navn)
navn = endre(navn)
print(navn)Hva skriver programmet ut?
x = 2 y = 3
def f(x):
y = x * 2 return y
def g():
return x + y
print(f(10))
print(g())
print(x, y)En student skal skrive et program som teller opp en poengsum tre ganger. Hun leverer:
poeng = 0
def legg_til(antall):
poeng = poeng + antall
legg_til(5)
legg_til(3)a) Hva skjer når programmet kjøres?
b) Forklar i én setning hvorfor.
c) Rett feilen uten å bruke globale variabler.
Fire feil dekker nesten alle scope-poengene som mistes:
- #12 — tro at en tilordning inne i en funksjon endrer utsiden. Den lager en
ny, lokal variabel. Utsiden er uendret.
- Kaste returverdien. nullstill(poeng) gjør ingenting synlig; det må stå
poeng = nullstill(poeng).
- Lese en global i stedet for å ta den inn som parameter. Funksjonen gir
riktig svar for det ene tilfellet, men er verken gjenbrukbar eller lesbar alene — og får sjelden full uttelling.
- Lese et navn du senere tilordner i samme funksjon. Da er navnet lokalt i
hele kroppen, og du får UnboundLocalError i stedet for den globale verdien.
Og den viktigste huskeregelen for det som kommer: nøyaktig denne mekanismen erself-fella i kap. 5.1. En konstruktør som skriverx = 5 i stedet for self._x = 5, lager en lokal variabel som forsvinner — og det er feilkode #1, den sensor planter oftest.
Begrepsbank
Begrepsbanken er flashcard- og repetisjonsstoff — den gjentar det du nettopp har lest. Hopp trygt over ved førstegangslesing; tidsanslaget for kapitlet gjelder kjernestoffet.
Feilmeldingen du får når du bruker et navn Python ikke kjenner.
De to vanligste årsakene i dette faget: du bruker en lokal variabel utenfor funksjonen den ble laget i, eller du kaller en funksjon før def-linja er lest.
Meldingen sier hvilket navn det gjelder — name 'mellomsvar' is not defined — og den er derfor en av de greieste feilene å rette.
Feilmeldingen du får når en funksjon leser et navn den også tilordner et sted i kroppen, før tilordningen har skjedd.
Årsaken er at ett likhetstegn gjør navnet lokalt i hele funksjonen — ikke bare fra og med den linja. Funksjonen ser derfor aldri etter den globale variabelen i det hele tatt.
Typisk oppskrift på feilen: poeng = poeng + antall inne i en funksjon derpoeng er ment å være den globale. Løsningen er alltid parameter inn, returverdi ut.
Å skrive x = f(x) i stedet for bare f(x).
Dette er den ene linja som skiller «funksjonen regnet ut noe» fra «programmet husker det». Uten likhetstegnet blir returverdien regnet ut og kastet, og ingenting utenfor funksjonen endres.
Det er en stille feil: programmet krasjer ikke, det gjør bare ingenting. Se etter den hver gang et program «ser riktig ut», men verdiene aldri endrer seg.
En funksjon som klarer seg med det den får inn, og som ikke er avhengig av noe utenfor seg selv.
Kjennetegnet: du kan lese den alene og vite nøyaktig hva den gjør, og du kan kalle den med hvilke som helst gyldige data.
Dette er standarden i fagets løsningsforslag, og målestokken sensor bruker. En funksjon som henter data fra globale variabler, gir sjelden full uttelling selv om den svarer riktig på det ene tilfellet i oppgaven.
Linjene som står uten innrykk og utenfor alle def-blokker. Det er dette
Python kjører ovenfra og ned når du starter programmet.
Variabler laget her lever i det globale scopet, og de finnes så lenge programmet kjører.
I fagets løsningsforslag står alle def-ene øverst og hovedprogrammet nederst.
Det er den formen du bør bruke på eksamen — og du skal ikke pakke det inn i noen egen startfunksjon.
Når du skriver dobbel(x), er det verdien til x som sendes inn, ikke navnet x.
Funksjonen får aldri vite hva variabelen din het, og den kan derfor ikke endre den. Skriver du dobbel(5) i stedet, oppfører funksjonen seg nøyaktig likt.
Denne presiseringen er det som gjør scope-regelen forståelig i stedet for bare noe å pugge — og den er det Oppgave 2 tester i mer sammensatt form når objektene kommer i kap. 5.4.
Helt lovlig, så lenge de lever i hvert sitt scope. Programmet kan ha én globalantall og en lokal antall i hver eneste funksjon.
Det er derfor du trygt kan bruke i, total og antall overalt uten å sjekke hva de heter andre steder.
Når du sporer: skriv opp navnene per funksjon, ikke per program. Da faller scope-spørsmålene på plass av seg selv.
Noe en funksjon gjør utover å returnere en verdi — for eksempel å skrive til skjermen eller endre noe utenfor seg selv.
I dette faget skal funksjoner helst være fri for sidevirkninger: de tar imot, regner ut og returnerer. Unntaket er metoder som med vilje endrer objektet sitt, og som du møter i kap. 5.2.
Ordet er verdt å kjenne fordi det forklarer hvorfor print inne i en funksjon sjelden er det oppgaven ber om: utskrift er en sidevirkning, ikke et svar.
En funksjon som bare bruker det den får inn, og bare gir fra seg en returverdi.
Kjennetegnet er at samme argumenter alltid gir samme svar, uansett hva som ellers har skjedd i programmet. med_paaslag(200, 0.10) gir 220 hver eneste gang.
Dette er standardformen for alle funksjonene i Oppgave 3, og den formen som er lettest å få full uttelling for: sensor kan lese funksjonen alene og se at den er riktig.
Tre spørsmål som avgjør ethvert scope-spørsmål på eksamen, i denne rekkefølgen:
1. Finnes det et likhetstegn med dette navnet inne i funksjonen? Hvis ja, er
navnet lokalt i hele kroppen.
2. Er navnet en parameter? Da er det også lokalt.
3. Ellers: funksjonen leser den globale.
Deretter det avgjørende oppfølgingsspørsmålet: tok hovedprogrammet imot returverdien med et likhetstegn? Hvis ikke, er ingenting endret utenfor.
Dette kapitlet er skrevet av Anthropics toppmodeller (Claude Opus og Claude Fable) og er foreløpig ikke manuelt gjennomgått — kvalitetskontrollen gjøres av uavhengige KI-agenter, og innmeldte feil rettes fortløpende. Funnet en feil? Meld fra, så retter vi den. Les mer om hvordan innholdet lages.
Skolesaga er en uavhengig læringsressurs og er ikke tilknyttet eller godkjent av Universitetet i Oslo. Dette er ikke offisielt studiemateriell. Les mer.