1.6 Funksjoner, scope og lambda

Definere funksjoner, flere returverdier, default-argumenter, variabel-scope og lambda — inkludert «funksjoner som tar funksjoner».

50 min
9 oppgaver
Funksjonerscopelambda
Din fremgang i kapitlet
0 / 9 oppgaver
Forkunnskaper:

- Kap. 1.1 — Variabler, datatyper, uttrykk og operatorpresedens — uttrykk, tilordning og presedens.
- Kap. 1.2 — Lister, indeksering og slicing — lister, som ofte er det funksjonene tar imot.
- Kap. 1.3 — Løkker — for, while, range og nøstede løkkerfor-løkka og akkumulatormønsteret, som brukes inne i nesten hver funksjon.
- Kap. 1.4 — Strenger, f-strenger og formatert utskrift — f-strenger, til de formaterte tabellene i eksemplene.
- Kap. 1.5 — Dictionaries og nøstede dictionaries — begrepet tuppel, som er det du får når en funksjon returnerer flere verdier.

Har du aldri programmert før, er Programmering med Python en mykere inngang du kan bla gjennom først.

Løkke 1 — Å definere og kalle en funksjon (~10 min)

En værstasjon rapporterer i celsius, men skal levere tall til en amerikansk database i fahrenheit. Omregningen er den samme hver gang, og du trenger den på tjue steder i programmet. Da skriver du den én gang og gir den et navn.

Det er alt en funksjon er: en oppskrift med et navn, som tar imot noen verdier og leverer en verdi tilbake. På eksamen er det aldri spørsmål om om du skal skrive en funksjon — oppgaven sier «skriv en funksjon som …». Spørsmålet er om signaturen og returverdien blir riktige.

`def navn(argumenter):`

Definerer en funksjon. Linja slutter med kolon, og alt som hører til funksjonen står med innrykk under — nøyaktig samme regel som for løkkekroppen i kap. 1.3.

Navnene i parentesen kalles parametre. De er navn funksjonen bruker internt, og de får verdier først når funksjonen kalles.

def celsius_til_fahrenheit(grader):
    return grader * 9 / 5 + 32

print(celsius_til_fahrenheit(20))
print(celsius_til_fahrenheit(-40))

Utskrift:

68.0
-40.0

Legg merke til at def-linja i seg selv ikke gjør noe: den lagrer oppskriften. Først kallet celsius_til_fahrenheit(20) kjører kroppen. Og at svaret er 68.0 og ikke 68 skyldes /, som alltid gir flyttall.

`return`

Leverer en verdi ut av funksjonen — og avslutter den umiddelbart. Alt som står etter return i samme funksjon, kjøres aldri.

En funksjon uten return gir verdien None, Pythons ord for «ingenting». Det er en av de vanligste sporingsfellene, fordi funksjonen kan se ut som den virker: den skriver noe ut, men leverer ikke noe tilbake.

def energi(masse, fart):
    e = 0.5 * masse * fart**2
    return e
    print('denne linja kjores aldri')

print(energi(2.0, 3.0))

Utskrift:

9.0

Sensor skiller nøye mellom «funksjonen skriver ut svaret» og «funksjonen returnerer svaret». Ber oppgaven om at funksjonen skal beregne noe, skal den returnere — ikke skrive ut.

Funksjon uten `return` gir `None`

En funksjon som bare skriver ut, leverer likevel en verdi tilbake: None. Skriver du den ut, står det None.

def skriv_rapport(navn, grader):
    print(navn, 'malte', grader, 'grader')

svar = skriv_rapport('Blindern', 4.2)
print(svar)

Utskrift:

Blindern malte 4.2 grader
None

To linjer utskrift fra to ulike print. Den første kommer fra inne i funksjonen, den andre fra det funksjonen ga tilbake. Prøver du å regne videre med svar, stopper programmet med en TypeErrorNone kan ikke legges sammen med et tall.

Parameter og argument
Parameteren er navnet i def-linja; argumentet er verdien du sender inn i kallet.

I def energi(masse, fart) er masse og fart parametre. I kallet energi(2.0, 3.0) er 2.0 og 3.0 argumenter.

Skillet er verdt å holde på fordi oppgavetekster bruker begge ordene, og fordi et parameternavn bare finnes inne i funksjonen. Det er det scope handler om, senere i kapitlet.

✏️Eksempel 1: Fra formel til funksjon

Den kinetiske energien til et legeme med masse mm og fart vv er E=12mv2\displaystyle E = \frac{1}{2}mv^2. Skriv en funksjon som regner den ut, og bruk den til å lage en liten tabell for masse 2,0 kg ved fartene 1, 2 og 3 meter per sekund.

Funksjonen tar de to størrelsene formelen trenger, og returnerer resultatet. Tabellen lages med en løkke utenfor funksjonen — funksjonen skal beregne, ikke skrive ut:

def energi(masse, fart):
    return 0.5 * masse * fart**2

for v in range(1, 4):
    print(f'{v:3d}{energi(2.0, v):8.2f}')

Utskrift:

  1    1.00
  2    4.00
  3    9.00

Tre ting sensor ser etter. Funksjonen har én oppgave og returnerer ett tall. Formelen står bare ett sted, så en rettelse virker overalt. Og løkkevariabelen v er et heltall fra range, mens energi gir flyttall — derfor 3d for det første og 8.2f for det andre i f-strengen, jf. kap. 1.4.

📝Oppgave 1

(Innstegsoppgave — ren gjengivelse og minimal sporing.) Se på funksjonen:

def omkrets(side):
    return 4 * side

Svar på hvert punkt for seg.

a) Hva heter parameteren?

b) Hva skriver print(omkrets(3)) ut?

c) Hva skriver print(omkrets(2.5)) ut?

d) Hva ville print(omkrets(3)) skrevet ut hvis linja return 4 * side ble byttet ut med print(4 * side)?

Løkke 2 — Argumenter: posisjon, navn og standardverdier (~10 min)

En vindkjølingsformel trenger temperatur, vindstyrke og en faktor som nesten alltid er den samme. Da vil du kunne kalle funksjonen med to argumenter i det normale tilfellet, og med tre når faktoren skal endres.

Python gir deg tre måter å sende argumenter på, og de kan kombineres. Å vite nøyaktig hvilken verdi som havner hvor, er en fast kodesporingsøvelse.

Posisjonsargumenter

Uten navn i kallet pares argumentene med parametrene i rekkefølge: første argument til første parameter, og så videre.

Det betyr at vindkjoling(4.0, 5.0) og vindkjoling(5.0, 4.0) er to helt forskjellige kall. Rekkefølgen i def-linja er kontrakten, og den må du lese før du kaller.

Nøkkelargumenter

Skriver du parameternavn=verdi i kallet, spiller rekkefølgen ingen rolle: vindkjoling(vind=5.0, temp=4.0) er samme kall som vindkjoling(4.0, 5.0).

Regelen er at alle posisjonsargumenter må komme først i kallet; navngitte argumenter kan så stå i hvilken som helst rekkefølge etterpå.

Navngitte argumenter er sensorvennlige i lange kall: solve(t_span, N) sier lite, mens plt.plot(x, y, label='S') sier alt. Du møter formen igjen i kap. 4.3.

Default-argumenter

En parameter kan få en standardverdi i def-linja: def vindkjoling(temp, vind, faktor=0.7). Da kan argumentet utelates i kallet, og standardverdien brukes.

Parametre med standardverdi må stå sist i def-linja.

def vindkjoling(temp, vind, faktor=0.7):
    return temp - faktor * vind

print(vindkjoling(4.0, 5.0))
print(vindkjoling(4.0, 5.0, 1.2))
print(vindkjoling(vind=5.0, temp=4.0))
print(vindkjoling(4.0, faktor=0.5, vind=5.0))

Utskrift:

0.5
-2.0
0.5
1.5

Linje 1 og 3 gir samme svar fordi de sender de samme verdiene — bare på hver sin måte. Standardverdier er hverdagskost i faget: steglengden h=1e-5 i numerisk derivasjon (kap. 6.1) og toleransen tol=1e-10 i testfunksjoner (kap. 3.1) er begge default-argumenter.

✏️Eksempel 2: Én funksjon, fire kall

En funksjon skal regne ut et vektet gjennomsnitt av noen målinger, der vekten normalt er 1,0. Skriv den, og vis hva som skjer når vekten dobles.

Vekten får en standardverdi, slik at det vanlige kallet blir kort:

def snitt(verdier, vekt=1.0):
    total = 0
    for v in verdier:
        total += v * vekt
    return total / len(verdier)

print(snitt([2, 4, 6]))
print(snitt([2, 4, 6], 2.0))

Utskrift:

4.0
8.0

Merk at total starter på 0 før løkka og at return står etter løkka, med mindre innrykk. Står return inne i løkka, avsluttes funksjonen etter første runde — det er en av de vanligste innrykksfeilene, og den er nummer #24 i feilkatalogen.

Variabelen heter total og ikke sum, fordi sum er navnet på en innebygd funksjon. Overskriver du det navnet, virker ikke sum(...) resten av programmet.

📝Oppgave 2

(Midtveisnivå, sjanger A — kodesporing.) Hva skriver programmet ut?

def f(a, b=2):
    return a * b

print(f(3), f(3, 4), f(b=5, a=1))

📝Oppgave 3
Eksamensnivå,…

Temperaturen i en avkjølende kopp kaffe kan modelleres som at hver måling er TT grader over romtemperaturen, som normalt er 21,0 grader.

a) Skriv funksjonen over_rom(maalinger, rom=21.0) som tar en liste med temperaturer og returnerer en ny liste med hvor mange grader hver måling ligger over romtemperaturen.

b) Vis to kall: ett med standardverdien og ett der romtemperaturen er 18,0 grader.

Løkke 3 — Å returnere flere verdier (~8 min)

Noen ganger har en beregning to svar som hører sammen: minste og største måling, summen og gjennomsnittet, tidspunktene og løsningen. Da vil du levere begge fra samme funksjon.

Python gjør dette enklere enn de fleste språk, og formen møter du igjen i hver eneste ODE-oppgave i Del 7, der løseren returnerer både tidspunktene og løsningen.

`return a, b` gir en tuppel

Komma etter return pakker verdiene i en tuppel — den faste sammenstillingen du møtte i kap. 1.5. Funksjonen returnerer altså fortsatt bare én verdi; den verdien inneholder bare flere ting.

Tar du imot resultatet i ett navn, får du hele tuppelen, og kan indeksere i den som i en liste.

Utpakking `x, y = f(...)`

Med to navn på venstresiden pakkes tuppelen ut, ett navn per verdi. Antall navn må stemme eksakt med antall returverdier.

def min_og_maks(verdier):
    minste = verdier[0]
    storste = verdier[0]
    for v in verdier:
        if v < minste:
            minste = v
        if v > storste:
            storste = v
    return minste, storste

lav, hoy = min_og_maks([4.2, -1.5, 5.0, 0.3])
print(lav, hoy)
par = min_og_maks([4.2, -1.5, 5.0, 0.3])
print(par)
print(par[1])

Utskrift:

-1.5 5.0
(-1.5, 5.0)
5.0

Legg merke til forskjellen i utskriften: to navn gir to tall skilt med mellomrom, ett navn gir tuppelen med parenteser og komma. Det er en tegn-for-tegn-detalj i sjanger A.

Begge startverdiene settes til verdier[0], ikke til 0 — ellers ville en liste med bare negative tall gitt feil svar på storste.

✏️Eksempel 3: Tre svar fra én funksjon

Skriv en funksjon som tar en liste med målinger og returnerer summen, gjennomsnittet og antallet — i den rekkefølgen. Bruk den, og skriv gjennomsnittet med to desimaler.

def statistikk(verdier):
    total = 0
    for v in verdier:
        total += v
    return total, total / len(verdier), len(verdier)

s, snitt, n = statistikk([3, 4, 8])
print(s, n)
print(f'{snitt:.2f}')

Utskrift:

15 3
5.00

Tre navn på venstresiden, tre verdier på høyresiden — rekkefølgen er det som binder dem sammen, ikke navnene. Skriver du snitt, s, n = statistikk(...), får snitt summen, uten at Python protesterer. Det er derfor oppgaveteksten alltid sier hvilken rekkefølge returverdiene skal ha, og hvorfor du skal lese den setningen to ganger.

Merk også at 5.00 har to desimaler fordi f-strengen ber om det, mens det bakenforliggende tallet er 5.0.

📝Oppgave 4

(Midtveisnivå, sjanger A — kodesporing med flere returverdier.) Hva skriver programmet ut? Skriv utskriften nøyaktig, linje for linje.

def deler(tall):
    return tall // 3, tall % 3

a, b = deler(11)
print(a, b)
svar = deler(11)
print(svar)
print(svar[0] + svar[1])

Løkke 4 — Scope: hvor en variabel finnes (~12 min)

— naturlig pausepunkt —

Nå kommer kapitlets mest lønnsomme kvarter. Scope betyr «gyldighetsområde»: hvilken del av programmet et navn finnes i.

Regelen er kort, men konsekvensene er ikke intuitive, og nettopp derfor er scope en fast gjenganger på midtveiseksamen. Du får et program på fem linjer og skal si hva som skrives ut — og svaret avhenger av om en tilordning traff den globale variabelen eller lagde en ny, lokal en.

Lokal variabel

Et navn som får verdi inne i en funksjon, finnes bare der. Det forsvinner når funksjonen er ferdig, og kan ikke brukes utenfra.

Parametrene er også lokale: masse i def energi(masse, fart) finnes bare inne i energi.

Praktisk konsekvens: to funksjoner kan trygt bruke samme navn i, total eller x uten å ødelegge for hverandre.

Globale variabler kan LESES inne i en funksjon

Finnes ikke navnet lokalt, leter Python utenfor funksjonen. Derfor virker dette:

grense = 3

def over_grensen(verdi):
    return verdi > grense

print(over_grensen(5), over_grensen(1))

Utskrift:

True False

Funksjonen leser grense utenfra, uten at den er sendt inn. Det virker, men det er dårlig vane: funksjonen blir avhengig av noe som står et helt annet sted. Sensor foretrekker at alt funksjonen trenger, kommer inn som argument — gjerne med standardverdi.

Tilordning inne i en funksjon lager en LOKAL variabel

Dette er kjernen i feil #13. Et likhetstegn inne i en funksjon lager alltid en ny, lokal variabel — det endrer aldri den globale med samme navn.

x = 5

def f():
    x = 10
    return x

print(f(), x)

Utskrift:

10 5

Funksjonen returnerer sin egen x, som er 10. Den globale x er fortsatt 5. To forskjellige variabler, samme navn.

Merk rekkefølgen: print(f(), x) kaller funksjonen først, men det spiller ingen rolle — kallet kan ikke endre den globale x uansett når det skjer.

Innholdet i en liste eller ordbok KAN endres av en funksjon

Regelen over gjelder tilordning. Endrer du innholdet i noe som ble sendt inn — med append, med indeksering eller med innsetting i en ordbok — ser du endringen utenfor også:

def legg_til_null(liste):
    liste.append(0)

maalinger = [1, 2]
legg_til_null(maalinger)
print(maalinger)

Utskrift:

[1, 2, 0]

Men en tilordning inne i funksjonen kobler bare det lokale navnet til noe nytt, og lar originalen i fred:

def nullstill(liste):
    liste = []
    return liste

maalinger = [1, 2]
nullstill(maalinger)
print(maalinger)

Utskrift:

[1, 2]

Skillet er alt: liste.append(0) endrer objektet, liste = [] bytter ut navnet. Det er samme skille som ligger bak feil #20 i kap. 1.5 — å endre argumentet i stedet for å bygge og returnere noe nytt.

✏️Eksempel 4: Samme navn på tre steder

Programmet under bruker navnet navn både utenfor og inne i en funksjon. Hva skriver det ut, og hvorfor?

navn = 'global'

def ytre():
    navn = 'lokal'
    print(navn)

ytre()
print(navn)

Utskrift:

lokal
global

Sporingen, linje for linje. Først settes den globale navn til 'global'. def-linja kjører ikke kroppen, bare lagrer den. Kallet ytre() lager en ny, lokal navn med verdien 'lokal', og skriver den ut. Når funksjonen er ferdig, forsvinner den lokale variabelen, og siste print finner den globale igjen, uforandret.

Mønsteret er hele scope-oppgaven på midtveis: to variabler med samme navn, og spørsmålet er hvilken av dem hvert print ser. Tegn to bokser — en for det globale nivået og en for funksjonen — og før hver tilordning inn i riktig boks. Da svarer du riktig hver gang.

📝Oppgave 5

(Midtveisnivå, sjanger A — scope.) Hva skriver programmet ut? Skriv utskriften nøyaktig, linje for linje.

faktor = 2

def skaler(verdi):
    faktor = 10
    return verdi * faktor

print(skaler(3))
print(faktor)
print(skaler(faktor))

📝Oppgave 6
Eksamensnivå, sjanger A

Hva skriver programmet ut? Skriv utskriften nøyaktig, linje for linje.

data = {'a': 1}
antall = 0

def oppdater(d, nokkel):
    antall = 99
    d[nokkel] = d.get(nokkel, 0) + 1
    return antall

print(oppdater(data, 'a'), oppdater(data, 'b'))
print(data)
print(antall)

Løkke 5 — Funksjoner som verdier, og lambda (~10 min)

Numerisk derivasjon, integrasjon og nullpunktsøking har én ting til felles: metoden er den samme uansett hvilken funksjon du behandler. Da vil du skrive metoden én gang og sende funksjonen inn som argument.

Det er dette som gjør at deriver(sin, 0.0) og deriver(kvadrat, 2.0) kan bruke samme kode. Og det er her lambda kommer inn: en måte å lage en liten funksjon på uten å gi den et eget navn med def.

Funksjonsnavnet uten parenteser er en verdi
dobbel er navnet på funksjonen; dobbel(7) er kallet. Uten parenteser får du selve funksjonen, og den kan lagres i en variabel og sendes videre som et hvilket som helst tall.

def dobbel(x):
    return 2 * x

g = dobbel
print(g(7))
print(type(dobbel))

Utskrift:

14
<class 'function'>

Parentesene er altså ikke pynt: de betyr «kjør nå». Å skrive plt.legend uten parenteser er en klassisk grunn til at ingenting skjer.

Funksjon som argument

En parameter kan ta imot en funksjon, og kalles med parenteser inne i kroppen. Da er metoden skrevet én gang og virker for alle funksjoner:

from math import sin

def verditabell(f, a, b, n):
    for i in range(n + 1):
        x = a + i * (b - a) / n
        print(f'{x:5.2f}{f(x):8.3f}')

verditabell(sin, 0.0, 1.0, 2)

Utskrift:

 0.00   0.000
 0.50   0.479
 1.00   0.841

Merk range(n + 1): n er antall intervaller, og da trengs n + 1 punkter for å få med begge endene. Det er feil #1 i feilkatalogen — den hyppigste i hele faget — og du møter den igjen i hvert eneste kapittel om numeriske metoder.

Legg også merke til at sin sendes inn uten parenteser.

`lambda x: uttrykk`

Lager en funksjon på én linje, uten def og uten return. lambda x: x**2 - 2 betyr nøyaktig det samme som:

def kvadrat(x):
    return x**2 - 2

Formen er lambda parametre: uttrykk. Etter kolon står ett uttrykk, som blir returverdien — ingen løkker, ingen flere linjer.

kvadrat = lambda x: x**2 - 2
print(kvadrat(3))
print(kvadrat(0))

Utskrift:

7
-2

Bruk lambda når funksjonen er kort og bare trengs på ett sted — typisk som argument til en annen funksjon. Skal den brukes flere steder, skriv def: det leses lettere.

`eval(uttrykkstreng)`

Tar en tekst som inneholder Python-kode, regner den ut og gir verdien. Da kan et uttrykk komme utenfra — fra brukeren eller fra kommandolinjen — i stedet for å stå fast i koden.

x = 4
uttrykk = '3*x + 1'
print(eval(uttrykk))
print(eval('2**3**2'))

Utskrift:

13
512

Uttrykket regnes ut med de variablene som finnes akkurat da, og med vanlige presedensregler — derfor 512 og ikke 64, siden ** regnes fra høyre.

Kombinert med lambda gir dette et program som kan behandle en funksjon brukeren skriver inn: f = eval('lambda x: x**2 - 2'). Mer om brukerinput i kap. 2.1.

✏️Eksempel 5: Samme tabellfunksjon, ny funksjon inn (eksamensnivå)

Bruk tabellfunksjonen fra definisjonen over til å lage en verditabell for f(x)=x22f(x) = x^2 - 2 på intervallet fra 0 til 2, med fire intervaller. Hvor skifter funksjonen fortegn?

Funksjonen trengs bare på dette ene stedet, så den skrives som en lambda rett inn i kallet:

def verditabell(f, a, b, n):
    for i in range(n + 1):
        x = a + i * (b - a) / n
        print(f'{x:5.2f}{f(x):8.3f}')

verditabell(lambda x: x**2 - 2, 0.0, 2.0, 4)

Utskrift:

 0.00  -2.000
 0.50  -1.750
 1.00  -1.000
 1.50   0.250
 2.00   2.000

Fortegnet skifter mellom x=1,00x = 1{,}00 og x=1,50x = 1{,}50, altså mellom rad 3 og rad 4. Det er nøyaktig det signalet en nullpunktsmetode leter etter, og du møter det igjen i kap. 6.1.

Fire intervaller ga fem rader — feil #1 i praksis. Og verditabell ble ikke rørt: den samme koden virker for sin og for annengradsfunksjonen, fordi funksjonen kommer inn som argument. Det er hele poenget med å behandle funksjoner som verdier.

📝Oppgave 7

(Midtveisnivå, sjanger E — «hvilket funksjonskall er riktig?».) Funksjonen verditabell(f, a, b, n) fra eksempel 5 er definert. Programmet skal skrive en tabell for funksjonen g(x)=3x+1g(x) = 3x + 1 fra 0 til 1 med to intervaller. Hvilken av linjene gjør det riktig?

AlternativLinje
averditabell(3*x + 1, 0.0, 1.0, 2)
bverditabell(lambda x: 3*x + 1, 0.0, 1.0, 2)
cverditabell(lambda x: 3*x + 1(0.0), 1.0, 2)
dverditabell(def g(x): return 3*x + 1, 0.0, 1.0, 2)

Velg ett alternativ og begrunn hvorfor de tre andre er feil.

📝Oppgave 8

(Midtveisnivå, sjanger A — funksjon som argument.) Hva skriver programmet ut?

def bruk(func, verdi):
    return func(verdi) + 1

print(bruk(lambda t: t * 10, 3))
print(bruk(abs, -5))

📝Oppgave 9
Eksamensnivå, sjanger…

En måling er «gyldig» dersom den oppfyller et krav som varierer fra oppgave til oppgave.

a) Skriv funksjonen tell_gyldige(verdier, krav) som tar en liste og en funksjon, og returnerer hvor mange av verdiene som oppfyller kravet. Funksjonen krav tar ett tall og returnerer True eller False.

b) Bruk den til å telle hvor mange av [4.2, -1.5, 5.0, 0.3, -0.8] som er over null, og hvor mange som er større enn 1.

c) Forklar hva som går galt hvis du kaller tell_gyldige(verdier, v > 0).

API- og konstruksjonsliste

Begrepsbank

Begrepsbanken er flashcard-/repetisjonsstoff — den gjentar det du nettopp har lest. Hopp trygt over ved førstegangslesing; tidsanslaget for kapitlet gjelder kjernestoffet.

Signatur

Funksjonens def-linje: navnet, parametrene, rekkefølgen deres og eventuelle standardverdier.

Oppgavetekster oppgir signaturen presist — «skriv funksjonen forward_euler(f, T, N, u0)» — og sensor sjekker den først. Bytter du om to parametre, virker koden fortsatt for deg, men ikke for den som kaller den. Les signaturen ordrett fra oppgaveteksten, og skriv den av.

Kallbar

En verdi er kallbar hvis den kan skrives med parenteser etter seg: funksjoner er kallbare, tall og lister er det ikke.

Ordet dukker opp i feilmeldingen TypeError: 'int' object is not callable, som nesten alltid betyr at du har sendt inn et tall der en funksjon var ventet — felle #19. I kap. 5.1 lager du dine egne kallbare objekter.

Hjelpefunksjon

En liten funksjon som gjør én ting, brukt av en større. Sensor liker dem: de gjør koden kort, og de kan rettes hver for seg.

Et typisk eksempel er å skille beregningen fra utskriften — én funksjon som regner og returnerer, og løkka som skriver ut, utenfor. Da kan den samme beregningen brukes i en testfunksjon senere, slik du gjør i kap. 3.1.

Repetisjon — de seks setningene som må sitte

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.