2.1 Funksjoner: def, parametere og return
Definere og kalle funksjoner, parametere, `return` (også flere returverdier og flere `return`-steder) — håndverket bak Oppgave 3 og 5 og alle metoder i Oppgave 4.
all kodeskriving på eksamen har. Oppgave 3 (~28 poeng) og Oppgave 5 (~9 poeng)
består utelukkende av «skriv en funksjon som …», og hver eneste metode i den store Oppgave 4 er en funksjon.
Til sammen er det cirka 83 % av poengene på settet. Ingen annen konstruksjon i faget bærer så mye.
Ett mønster går igjen oftere enn noe annet, og det har et navn i denne boka:
flagg med tidlig retur. Du skal svare ja eller nei om en hel samling — «er
alle positive?», «finnes det en som mangler?» — og fasiten løser det ved å returnere False med én gang et moteksempel dukker opp, og True først etter løkka. Å returnere for tidlig er feilkode #7, og den er godt dokumentert i arkivet.
Dette må sitte — bokas høyeste av tre nivåer.
Forkunnskaper — sist du var her
Kap. 1.3 — Løkker: for, while og range, og gjennom den
kap. 1.1 og kap. 1.2.
De tre tingene du bruker mest her:
- teller- og akkumulatormønsteret: startverdi før løkka, oppdatering inne i
den, bruk etter,
- gjennomløping av en tekst med for tegn in tekst:,
- betingelser, og at bare den første sanne grenen i en kjede kjøres.
Kort oppfrisket:
antall = 0 for tegn in "kodeord":
if tegn == "o":
antall = antall + 1 print(antall)Utskrift:
2I dette kapitlet flytter vi nettopp slik kode inn i en funksjon, slik at den kan gjenbrukes med ulike data.
Løkke 1 — Å definere og kalle en funksjon (~10 min)
Koden i oppfriskingen over teller o-ene i ett bestemt ord. Vil du telle o-ene i et annet ord, må du skrive alt på nytt.
En funksjon løser dette: du skriver oppskriften én gang, gir den et navn, og lar den ta imot det den skal jobbe på.
En navngitt bit kode som utfører en bestemt oppgave, og som du kan bruke om igjen med ulike data.
To ting skjer med en funksjon, og de er lett å blande:
- Du definerer den én gang, med def. Da skjer det ingenting ennå — Python
bare noterer oppskriften.
- Du kaller den så mange ganger du vil, ved å skrive navnet med parentes
etter. Først da kjøres kroppen.
Alt du har brukt så langt med parentes etter navnet — print(...), int(...),len(...) — er funksjoner. Nå skal du skrive dine egne.
Definerer en funksjon. Linja avsluttes med kolon, og kroppen står innrykket under:
def hils(navn):
print("Hei,", navn)
hils("Ada")
hils("Nora")Utskrift:
Hei, Ada
Hei, NoraLegg merke til at kroppen kjøres to ganger, én gang per kall — men den er skrevet én gang.
Definisjonen må stå før kallet i programmet. Python leser filen ovenfra, og kjenner ikke et navn den ikke har sett ennå.
To ord for to sider av samme sak:
- Parameter er navnet i definisjonen: navn i def hils(navn):. Det er en
variabel som får en verdi når funksjonen kalles.
- Argument er verdien du sender inn ved kallet: "Ada" i hils("Ada").
En funksjon kan ha flere parametere, skilt med komma, og da er rekkefølgen avgjørende: det første argumentet havner i den første parameteren.
def vis(fornavn, etternavn):
print(fornavn, etternavn)
vis("Ada", "Berg")
vis("Berg", "Ada")Utskrift:
Ada Berg
Berg AdaPython vet ingenting om hva navnene betyr — den fyller bare inn i rekkefølge.
Sender en verdi tilbake til den som kalte funksjonen:
def dobbel(tall):
return tall * 2
svar = dobbel(21)
print(svar)
print(dobbel(5) + 1)Utskrift:
42
11To ting skjer samtidig når return kjøres. Funksjonen avsluttes umiddelbart
— ingen linjer under blir kjørt. Og verdien blir resultatet av kallet, slik at du kan lagre den i en variabel eller regne videre med den, som på siste linje.
return er derfor ikke det samme som print. print viser noe på skjermen;return gir en verdi tilbake til programmet.
Disse to gjør helt forskjellige ting, og en funksjon som skriver ut i stedet for å returnere, er nesten alltid ubrukelig på eksamen.
def dobbel_skriv(tall):
print(tall * 2)
def dobbel_returner(tall):
return tall * 2
a = dobbel_skriv(5)
b = dobbel_returner(5)
print(a, b)Utskrift:
10
None 10Den første funksjonen viste 10 på skjermen, men ga ingenting tilbake — derfor er a lik None. Den andre ga verdien tilbake, og b er 10.
Regelen på eksamen: ber oppgaven om en funksjon som «returnerer» noe, skal
det stå return. Skriver du print i stedet, kan ikke resten av programmet bruke resultatet, og deloppgavene som bygger videre, faller sammen.
Tellekoden fra forkunnskapene teller o-ene i ett bestemt ord. Gjør den om til en funksjon som teller hvor mange ganger et gitt tegn finnes i en gitt tekst, og vis at den kan brukes flere ganger.
Det som varierer, blir parametere; det som skal ut, blir returverdien:
def tell_tegn(tekst, tegn):
antall = 0 for t in tekst:
if t == tegn:
antall = antall + 1 return antall
print(tell_tegn("kodeord", "o"))
print(tell_tegn("bibliotek", "b"))
print(tell_tegn("bibliotek", "z"))Utskrift:
2
2
0Tre observasjoner som er verdt poeng:
- Telleren lever inne i funksjonen. antall = 0 står i kroppen, så den
starter på null ved hvert nytt kall. Det er nettopp derfor funksjonen kan brukes om igjen.
- return står etter løkka, med samme innrykk som for. Sto den inne i
løkka, ville funksjonen returnert etter første tegn.
- Null er et gyldig svar. Siste kall finner ingen z, og returnerer 0
uten at noe går galt. Å håndtere «finnes ikke» uten å krasje er feilkode #6, og her løser mønsteret det av seg selv.
(Innstegsoppgave — ren gjengivelse.) Se på denne funksjonen:
def trippel(tall):
return tall * 3a) Hva er navnet på parameteren?
b) Hva gir kallet trippel(4)?
c) Hva skriver print(trippel(2) + trippel(3)) ut?
d) Hva skjer hvis du skriver trippel(4) på en linje for seg, uten print
og uten å lagre svaret?
Løkke 2 — None, flere return og tidlig retur (~14 min)
Nå kommer kapitlets viktigste mønster, og det som gir flest poeng på eksamen.
En funksjon kan ha flere return-setninger. Den første som kjøres, avslutter funksjonen — og det er nettopp den egenskapen som gjør «er alle …?»-spørsmål enkle å svare på.
Kommer en funksjon til slutten av kroppen uten å ha kjørt en return, gir den tilbake None. Det er en egen verdi i Python, og den betyr «ingenting».
def uten_retur(x):
y = x * 2
print(uten_retur(5))Utskrift:
NoneNone er den vanligste kilden til forvirrende feil hos nybegynnere: programmet krasjer ikke der funksjonen ble skrevet, men et helt annet sted der noen prøver å regne med None.
Ser du None i en utskrift, er svaret nesten alltid at det mangler et return.
En funksjon kan ha så mange return den vil. Den som kjøres først, avslutter funksjonen:
def karakter(poeng):
if poeng >= 90:
return "A" elif poeng >= 60:
return "C" return "F"
print(karakter(95), karakter(72), karakter(20))Utskrift:
A C FLegg merke til at siste linje ikke trenger else: kommer programmet dit i det hele tatt, betyr det at ingen av de tidligere return-ene kjørte.
Dette er en ryddig og helt vanlig form i fagets løsningsforslag, og den er lett å spore: finn den første betingelsen som er sann, så vet du hva funksjonen gir.
Bokas tredje kodemønster, og det som går igjen oftest i Oppgave 3. Det brukes når du skal svare ja eller nei om en hel samling.
Oppskriften har to faste steg:
1. Inne i løkka: finner du ett moteksempel, returner False med én gang.
2. Etter løkka: har du kommet hit, fantes det ingen moteksempler — returner
True.
def bare_sifre(tekst):
for tegn in tekst:
if tegn < "0" or tegn > "9":
return False return True
print(bare_sifre("2024"))
print(bare_sifre("20x4"))Utskrift:
True
FalseDet avgjørende er hvor return True står. Den hører etter løkka, med
samme innrykk som for. Står den inne i løkka, avgjør det aller første tegnet hele svaret — og funksjonen blir gal for alt annet enn ettegnsdata.
Mønsteret virker like godt på lister, og du bruker det igjen i
kap. 3.1.
Dette er den mest dokumenterte feilen i fagets løsningsforslag, og den ser tilforlatelig ut:
def bare_sifre_feil(tekst):
for tegn in tekst:
if tegn < "0" or tegn > "9":
return False else:
return True
print(bare_sifre_feil("20x4"))Utskrift:
TrueFunksjonen svarer True på en tekst som inneholder en x. Grunnen er atreturn True står inne i løkka: den kjøres etter det aller første tegnet, og resten blir aldri undersøkt.
Kjenn igjen fella på formen. Har du både return False og return True inne
i samme løkke, er noe nesten alltid galt. Det ene svaret krever at du har sett
alle elementene, og det kan du bare vite når løkka er ferdig.
Moteksempelet du skal gi, hvis oppgaven ber om det: kalletbare_sifre_feil("20x4") gir True, men skulle gitt False.
Skriv to funksjoner: én som svarer om alle tegnene i en tekst er store bokstaver, og én som svarer om minst ett av dem er det. Hvorfor står return på motsatt sted i de to?
Den ene leter etter et moteksempel, den andre etter et eksempel:
def alle_store(tekst):
for tegn in tekst:
if tegn >= "a" and tegn <= "z":
return False return True
def noen_store(tekst):
for tegn in tekst:
if tegn >= "A" and tegn <= "Z":
return True return False
print(alle_store("ABC"), alle_store("AbC"))
print(noen_store("abc"), noen_store("abC"))Utskrift:
True False
False TrueMønsteret er speilvendt:
| Spørsmål | Inne i løkka | Etter løkka |
|---|---|---|
| «er alle …?» | return False ved første moteksempel | return True |
| «finnes det noen …?» | return True ved første treff | return False |
Begge returnerer med én gang de vet svaret, og begge venter til etter løkka med det svaret som krever at hele samlingen er sett.
Slik ville sensor sett på det: har du riktig løkke og riktig betingelse, men
har byttet om de to
return-ene, er strukturen riktig og feilen liten — det gir uttelling. Har du derimot begge return-ene inne i løkka, er selve ideen borte.Hva skriver programmet ut?
def sjekk(tekst):
for tegn in tekst:
if tegn == "a":
return "funnet" return "ikke funnet"
print(sjekk("banan"))
print(sjekk("kork"))Skriv funksjoneningen_mellomrom(tekst) som returnerer True hvis teksten ikke inneholder noen mellomrom, og False ellers.
a) Skriv funksjonen med flagg og tidlig retur.
b) Hvor må return True stå, og hva blir galt hvis den står inne i løkka?
c) Hva returnerer funksjonen for den tomme teksten "", og er det rimelig?
Løkke 3 — Flere returverdier og funksjoner som samarbeider (~12 min)
To siste teknikker. Den ene lar en funksjon gi tilbake mer enn én verdi; den andre lar funksjoner bruke hverandre — som er selve grunnformen i den store
Oppgave 4.
En funksjon kan returnere flere verdier på én gang, skilt med komma. Ved kallet tar du imot dem med like mange navn:
def del_og_rest(a, b):
return a // b, a % b
hele, rest = del_og_rest(17, 5)
print(hele, rest)Utskrift:
3 2Antall navn på venstre side må stemme med antall verdier funksjonen returnerer.
Dette kalles å pakke ut returverdiene. Det er en ryddig løsning når to opplysninger naturlig hører sammen, og den finnes i fagets egne løsningsforslag.
En funksjon kan bruke en annen funksjon i kroppen sin, akkurat som du kaller den fra hovedprogrammet:
def er_vokal(tegn):
return tegn == "a" or tegn == "e" or tegn == "i" or tegn == "o" or tegn == "u"
def tell_vokaler(tekst):
antall = 0 for tegn in tekst:
if er_vokal(tegn):
antall = antall + 1 return antall
print(tell_vokaler("bibliotek"))Utskrift:
4Legg merke til hvordan if er_vokal(tegn): leser nesten som norsk. Det er poenget: en godt navngitt hjelpefunksjon gjør den ytre funksjonen kortere og lettere å lese.
På eksamen er dette uttrykkelig tillatt — du kan legge til egne hjelpefunksjoner ved behov, så lenge du kommenterer kort hva de gjør.
En liten funksjon som finnes for å gjøre en annen funksjon enklere. er_vokal over er et typisk eksempel: den gjør én ting, og den gjør den godt.
To grunner til at dette lønner seg på eksamen. Du får delkarakter per deloppgave, og en hjelpefunksjon som virker, gir uttelling selv om den store funksjonen ikke ble ferdig. Og du kan bruke en metode oppgaveteksten beskriver selv om du ikke selv rakk å skrive den — da kaller du den bare, som om den var ferdig.
Regelen for gode hjelpefunksjoner er enkel: én oppgave per funksjon, og et navn som sier hva den svarer på.
Python leser filen ovenfra og ned. Kaller du en funksjon før def-linja er lest, kjenner den ikke navnet:
print(dobbel(4))
def dobbel(tall):
return tall * 2Dette stopper med NameError: name 'dobbel' is not defined. Blokken er merket som ikke-kjørbar fordi den er en plantet feil.
I praksis betyr regelen at alle def-ene står øverst i besvarelsen din, og kallene under. Slik ser fasitkoden i faget ut, og slik bør din se ut.
Én presisering som ofte forvirrer: at én funksjon kaller en annen inne i kroppen sin, er helt greit uansett rekkefølge på definisjonene — kroppen kjøres jo ikke før begge er lest. Det er bare selve kallet som må komme sist.
Skriv lengste_ord(setning) som returnerer det lengste ordet i en setning, ogantall_lange(setning, grense) som teller hvor mange ord som er lengre enn grensen. Bruk en hjelpefunksjon som deler setningen i ord.
Bygg opp svaret med de konstruksjonene du har til nå: gjennomløping tegn for tegn, teller og akkumulator.
Del problemet i to. Den ene funksjonen finner ordgrensene; de to andre bruker dem.
def ord_lengder(setning):
lengder = "" n = 0 for tegn in setning:
if tegn == " ":
lengder = lengder + str(n) + " " n = 0 else:
n = n + 1 return lengder + str(n)
print(ord_lengder("kort og lengre ord"))Utskrift:
4 2 6 3Hjelpefunksjonen gir lengdene som en tekst. Nå kan de to andre bruke den:
def ord_lengder(setning):
lengder = "" n = 0 for tegn in setning:
if tegn == " ":
lengder = lengder + str(n) + " " n = 0 else:
n = n + 1 return lengder + str(n)
def lengste_lengde(setning):
beste = 0 n = 0 for tegn in setning:
if tegn == " ":
if n > beste:
beste = n n = 0 else:
n = n + 1 if n > beste:
beste = n return beste
print(lengste_lengde("kort og lengre ord"))
print(ord_lengder("ett"))Utskrift:
6
3Slik ville sensor sett på det: to ting gir uttelling her uavhengig av
hverandre. Den ene er at telleren nullstilles ved hvert mellomrom. Den andre er
oppgjøret etter løkka — de tre linjene som håndterer det siste ordet, som
ikke har noe mellomrom etter seg. Glemmer du dem, blir alle svar riktige unntatt for det siste ordet, og det er en liten feil med riktig idé bak.
I kap. 3.3 får du en ferdig konstruksjon som deler en tekst i ord for deg. Poenget her er mekanikken under.
Hva skriver programmet ut?
def hjelp(x):
return x + 1
def hoved(x):
y = hjelp(x)
if y > 5:
return "stor" return "liten"
print(hoved(4), hoved(5))Skriv sum_til(n) som returnerer summen av alle heltallene fra 1 til og med n.
a) Skriv funksjonen med akkumulatormønsteret.
b) Hva returnerer sum_til(0)?
c) Hvorfor er det viktig at return står etter løkka og ikke inne i den?
Hva skriver programmet ut?
def f(tekst):
for tegn in tekst:
if tegn == "e":
return True return False
def g(tekst):
if f(tekst):
return "har e" return "ingen e"
print(g("tekst"), g("kort"))
print(f(""))En student skal skrive en funksjon som svarer om alle tegnene i en tekst er like. Hun leverer:
def alle_like(tekst):
for tegn in tekst:
if tegn == tekst[0]:
return True else:
return FalseUttrykket tekst[0] henter det første tegnet i teksten — det er alt du trenger å vite om indeksering her; den kommer for fullt i
kap. 3.3.
a) Hva returnerer alle_like("aab")?
b) Gi et konkret moteksempel som viser at funksjonen er gal, og forklar i én
setning hvorfor.
c) Rett feilen.
En funksjon kan returnere mer enn én verdi. Du skriver verdiene etterreturn med komma mellom, og tar dem imot med like mange navn på venstre side av likhetstegnet:
def del_og_rest(teller, nevner):
hele = teller // nevner rest = teller % nevner return hele, rest
antall, til_overs = del_og_rest(17, 5)
print(antall, til_overs)Utskrift:
3 2Antallet navn må stemme med antallet verdier: to verdier og tre navn gir en feilmelding, og det samme gjør det motsatte. Tar du imot med ett navn i stedet, får du begge verdiene samlet som et par i parentes — det er verdt å kjenne igjen i en sporingsoppgave.
Skriv min_og_maks(a, b, c) som returnerer to verdier: den minste og den største av de tre argumentene. Bruk bare sammenligninger og if — ingen ferdige hjelpefunksjoner.
Vis også hvordan du tar imot de to verdiene ved kallet.
Fem feil koster nesten alle poengene som mistes på funksjoner:
- #7 — for tidlig return. Har du både return True og return False inne
i samme løkke, er noe nesten alltid galt. Det svaret som krever at du har sett alt, hører etter løkka.
- print der oppgaven ba om return. Funksjonen viser riktig svar på
skjermen, men gir None tilbake — og de neste deloppgavene faller sammen.
- Glemt return helt. Funksjonen gir None, og feilen viser seg et helt
annet sted i programmet.
- Feil argumentrekkefølge. Python fyller inn i rekkefølge og aner ikke hva
navnene betyr.
- #6 — kanttilfellene. Hva gjør funksjonen din med tom inndata, eller når
det ikke finnes noe treff? Sjekk det før du går videre.
Og én formfeil: kall før definisjon. Alle def-ene øverst, kallene under.
Begrepsbank
Begrepsbanken er flashcard- og repetisjonsstoff — den gjentar det du nettopp har lest. Hopp trygt over ved førstegangslesing; tidsanslaget for kapitlet gjelder kjernestoffet.
Linjene som kjøres når funksjonen kalles, altså de innrykkede linjene underdef.
Alt som lages i kroppen — tellere, mellomregninger — finnes bare mens funksjonen kjører, og forsvinner når den returnerer. Det er derfor en teller kan settes til null i kroppen uten at gamle kall påvirker nye.
Reglene for hva som er synlig hvor, kalles scope, og de er tema i
kap. 2.2.
Å ta imot flere returverdier med like mange navn: hele, rest = del_og_rest(17, 5).
Antallet må stemme. To verdier og tre navn gir en feilmelding, og det samme gjør det motsatte.
Tar du imot med ett navn i stedet, får du begge verdiene samlet som et par — det er derfor print(min_og_maks(4, 4, 4)) viser (4, 4) med parentes.
Et funksjonskall er et uttrykk med en verdi, og kan derfor brukes overalt en verdi kan stå: i et regnestykke (dobbel(5) + 1), som argument til en annen funksjon (print(tell_tegn(...))), eller som betingelse (if f(tekst):).
Den siste formen forutsetter at funksjonen returnerer True eller False, og den er vanlig i fagets fasiter fordi den leser som norsk.
Du trenger altså ikke lagre svaret i en variabel først — men gjør det gjerne hvis det gjør koden lettere å lese.
Skriver du bare trippel(4) på en linje for seg, kjøres funksjonen, verdien regnes ut, og så kastes den.
Det er ikke en feil, og for funksjoner som skriver ut noe selv, er det helt riktig bruk. Men returnerer funksjonen en verdi du trenger, er en slik linje nesten alltid en forglemmelse: du har glemt print eller glemt å lagre svaret.
I en sporingsoppgave betyr det at linja ikke gir noen utskrift.
To atskilte hendelser, og skillet er verdt å ha helt klart.
Definisjonen (def dobbel(tall): med kroppen under) forteller Python hvordan
noe skal gjøres. Når linja leses, skjer det ingenting annet enn at navnet blir kjent.
Kallet (dobbel(4)) er det som faktisk kjører kroppen. Uten et kall blir en
funksjon aldri brukt, uansett hvor riktig den er skrevet.
I en sporingsoppgave betyr det at du kan hoppe over hele def-blokken ved første gjennomlesing, og først lese kroppen når du kommer til et kall.
Argumentene fylles inn i parameterne i den rekkefølgen de står — det første argumentet i den første parameteren, og så videre.
Python vet ingenting om hva navnene betyr. Kaller du vis("Berg", "Ada") når funksjonen forventer fornavn først, får du ingen feilmelding; du får bare feil svar.
Antallet må derimot stemme: for få eller for mange argumenter gir en feilmelding med én gang.
En funksjon som returnerer True eller False. er_vokal(tegn) ogingen_mellomrom(tekst) er begge boolske.
Slike funksjoner kan brukes direkte som betingelse: if er_vokal(tegn):.
Det leser som norsk, og det er den formen fagets løsningsforslag bruker.
Navngi dem som et ja-eller-nei-spørsmål — er_…, har_…, alle_…, ingen_…
— så vet leseren hva et True betyr uten å lese kroppen.
Speilbildet av flagg med tidlig retur, brukt når du leter etter ett treff i stedet for å kreve at alle oppfyller noe:
- inne i løkka: return True ved første treff,
- etter løkka: return False.
Merk hvilket svar som står hvor. Det svaret som kan avgjøres av ett enkelt element, hører inne i løkka; det som krever at hele samlingen er undersøkt, hører etter.
Blander du de to mønstrene, får du feilkode #7.
Du kan skrive bare return på en linje. Da avsluttes funksjonen umiddelbart, og den gir None tilbake.
Formen brukes når funksjonen skriver ut noe selv og du vil avbryte tidlig, for eksempel etter en feilmelding til brukeren.
Ber oppgaven om en verdi, er en bar return derimot nesten alltid en forglemmelse — svaret ble aldri sendt tilbake.
Variabler som lages inne i en funksjonskropp, finnes bare mens funksjonen kjører, og lages på nytt ved hvert kall.
Det er derfor antall = 0 i tell_tegn ikke husker noe fra forrige kall — hvert kall starter med blanke ark. Egenskapen er hele grunnen til at en funksjon kan brukes om igjen med nye data.
Den samme egenskapen er kilden til self-fella i
kap. 5.1: en variabel som bare lages lokalt, forsvinner når metoden er ferdig.
Ett konkret kall med konkrete verdier som viser at en funksjon gir galt svar:
«alle_like("aab") gir True, men skulle gitt False».
Formen er et eksplisitt krav på eksamen. En forklaring i ord om at «funksjonen håndterer ikke flere ulike tegn» er en halv besvarelse; moteksempelet er en hel.
Slik finner du et: let etter et kanttilfelle — tom inndata, ett element, like verdier, eller data der feilen slår inn først etter første runde.
Sjangeren har eget kapittel i kap. 4.2.
Funksjonsnavnet skal si hva funksjonen gir, ikke hvordan den regner det ut.tell_tegn, lengste_lengde og er_vokal er gode navn; funksjon2 og gjor er det ikke.
Norske navn er helt greie og er stilen i fagets egne løsningsforslag — men
ingen æ, ø eller å i koden. Skriv stoerst, laan, baat.
Bruk aldri navnet på en innebygd funksjon (sum, len, input, str) — det er feilkode #13, og den er stum.
På eksamen har du lov til å kalle en funksjon eller metode som er beskrevet i oppgaveteksten, selv om du ikke selv fikk skrevet den — eller ikke rakk å bli ferdig med deloppgaven der den skulle stå.
Dette er grunnen til at man aldri skal stå fast. Klarte du ikke deloppgave c, skriver du likevel deloppgave d som om c var ferdig, og kaller funksjonen derfra.
Det samme gjelder dine egne hjelpefunksjoner: du kan legge til dem du trenger, så lenge du kommenterer kort hva de gjør.
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.