3.6 Tupler

Uforanderlige tupler, parallelle tupler (score/bokstav/vekt) og pakking/utpakking — måten karaktergrenser og faste tabeller lagres på.

35 min
6 oppgaver
Tupler
Din fremgang i kapitlet
0 / 6 oppgaver
Forkunnskaper

- kap. 3.1 — lister, indeksering, utsnitt og in-testen. Et tuppel oppfører seg som ei liste på alle disse punktene.
- kap. 2.1 — funksjoner med return, fordi flere returverdier er tupler.
- kap. 3.5 — ordbøker, for .items() og for tuppelet som nøkkel.

Ett ord før vi begynner: uforanderlig betyr at verdien ikke kan endres etter at den er laget. Strenger er uforanderlige — det så du i kap. 3.3 — og tupler er det samme for samlinger.

Løkke 1 — tuppelet og uforanderligheten (~8 min)

Karaktergrensene i et emne er 90, 80, 70 og 60 poeng. De er bestemt før eksamen og skal ikke røres av programmet. Legger du dem i ei liste, kan hvilken som helst linje senere i koden gjøre grenser[0] = 50 ved et uhell, og da er hele karaktersettingen feil uten at noe krasjer.

Et tuppel er den samme samlingen, men låst. Du skriver det med runde parenteser i stedet for hakeparenteser, og Python nekter enhver endring. Det er den eneste virkelige forskjellen fra ei liste — alt annet, indeksering, utsnitt, len og in, er likt.

Tuppelet `(a, b, c)`

En ordnet samling verdier under ett navn, akkurat som ei liste, men skrevet med runde parenteser: grenser = (90, 80, 70, 60).

Rekkefølgen er en del av dataene, og den samme verdien kan stå flere ganger. Verdiene kan være av forskjellig type i samme tuppel — ('Ås', 4, 12.5) er helt vanlig, og er nettopp slik en rad fra et register ser ut. Det tomme tuppelet er ().

Uforanderlighet

Et tuppel kan ikke endres etter at det er laget. grenser[0] = 50 stopper programmet med TypeError: 'tuple' object does not support item assignment, og det finnes verken append, remove, sort eller reverse på et tuppel.

Det du kan gjøre, er å lage et nytt tuppel: grenser + (50,) gir et nytt tuppel med fem verdier, og det gamle står urørt. Uforanderligheten er ikke en begrensning, men en garanti: leser du at noe er et tuppel, vet du at ingen linje lenger nede har endret det.

Ettelementstuppelet `(5,)`

Det er kommaet som lager tuppelet, ikke parentesene. (5) er bare tallet 5 med en parentes rundt, mens (5,) er et tuppel med ett element.

Derfor er len((5)) en feilmelding, mens len((5,)) er 1. Det samme kommaet gjør at par = 3, 7 er et fullgyldig tuppel uten parenteser i det hele tatt. Parentesene er bare til for lesbarheten — bortsett fra når du trenger dem for å avgrense, for eksempel inne i en liste av tupler.

`len(t)`, `t[i]`, `t[a:b]` og `in` på tupler

Alt som bare leser fra ei liste, virker likt på et tuppel. len(t) gir antall elementer, t[0] det første, t[-1] det siste, og 'Ås' in t tester medlemskap.

Utsnitt virker også, og gir et nytt tuppel: grenser[:2] gir (90, 80). Merk at et utsnitt av et tuppel aldri gir ei liste — typen følger med. Det er bare de endrende operasjonene som mangler.

`tuple(liste)` og `list(tuppel)`

Konverterer begge veier. tuple([1, 2, 3]) gir (1, 2, 3), og list((1, 2, 3)) gir [1, 2, 3].

Den vanligste bruken er å låse et resultat før det returneres, eller motsatt: å låse opp et tuppel du trenger å sortere. sorted(t) gir forresten alltid ei liste, uansett hva du gir den — så skal svaret være et tuppel, må du pakke det inn igjen med tuple(...).

✏️Eksempel 1: karaktergrensene som tuppel

Legg karaktergrensene 90, 80, 70 og 60 i et tuppel. Skriv ut hele tuppelet, den første og den siste grensen, de to øverste, og sjekk om 75 er en av grensene. Vis også hva som skjer når du prøver å endre en av dem.

Alt som leser, ser ut akkurat som på ei liste:

grenser = (90, 80, 70, 60)
print(grenser)
print(grenser[0], grenser[-1])
print(grenser[:2])
print(75 in grenser)
print(len(grenser))

Utskrift:

(90, 80, 70, 60)
90 60
(90, 80)
False
4

Legg merke til at tuppelet skrives ut med runde parenteser, og at utsnittet grenser[:2] også er et tuppel. På en kodesporingsoppgave er dette forskjellen som gir poenget: [90, 80] er feil svar når (90, 80) er riktig.

Forsøk på endring stopper programmet. Under er siste linje av feilmeldingen du får:

TypeError: 'tuple' object does not support item assignment

Vil du ha andre grenser, lager du et nytt tuppel i stedet:

grenser = (90, 80, 70, 60)
nye = (95,) + grenser[1:]
print(nye)
print(grenser)

Utskrift:

(95, 80, 70, 60)
(90, 80, 70, 60)

Det gamle tuppelet står urørt. Merk kommaet i (95,) — uten det er (95) bare tallet 95, og 95 + grenser[1:] ville stoppet med en feilmelding.

📝Oppgave 1

(Innstegsoppgave, sjanger B — kodesporing, altså «hva skriver dette programmet ut?».) Skriv alle fire linjene nøyaktig. Vær nøye med hvilke parenteser som brukes hvor.

t = (4, 9, 2, 9)
print(t[1])
print(t[1:3])
print(len(t))
print(9 in t, 5 in t)

Løkke 2 — pakking, utpakking og flere returverdier (~10 min)

En funksjon som skal gi tilbake både den laveste og den høyeste temperaturen i en måling, har et problem: return gir bare én verdi. Løsningen er å pakke de to verdiene sammen i ett tuppel, og la mottakeren pakke dem ut igjen.

Dette er den vanligste bruken av tupler i faget, og den er så innebygd i Python at du knapt ser tuppelet.

Pakking: `par = a, b`

Skriver du flere verdier med komma mellom, samler Python dem i ett tuppel. Parentesene er valgfrie: par = 3, 7 gir det samme som par = (3, 7).

Det er dette som gjør at return laveste, hoyeste virker: verdiene pakkes til ett tuppel på veien ut av funksjonen. Skriver du print(par) etterpå, ser du parentesene igjen.

Utpakking: `x, y = par`

Fordeler elementene i et tuppel på flere variabler på én linje. x, y = (3, 7) setter x til 3 og y til 7.

Antallet må stemme eksakt. Er tuppelet lengre eller kortere enn antall variabler, stopper programmet med ValueError: too many values to unpack eller not enough values to unpack. Utpakking virker også i et løkkehode — for k, v in d.items(): er nøyaktig denne mekanismen.

Bytte to verdier: `a, b = b, a`

Høyresiden pakkes til et tuppel først, og deretter pakkes det ut på venstresiden. Derfor bytter denne ene linja innholdet i to variabler, uten en hjelpevariabel.

Det samme skjer i boblesorteringen senere i boka: x[j], x[j+1] = x[j+1], x[j]. Poenget er at begge de gamle verdiene er lest inn i tuppelet før noen av dem overskrives — skriver du det i to linjer uten hjelpevariabel, mister du den ene.

`return a, b` — flere returverdier

En funksjon kan bare returnere én verdi, men den verdien kan være et tuppel. return laveste, hoyeste pakker de to sammen.

Mottakeren kan velge: lav, hoy = maal(...) pakker ut i to variabler, mens svar = maal(...) beholder tuppelet samlet og bruker svar[0] og svar[1]. Begge er gyldige. Merk at du ikke kan skrive return a; return b — den andre linja kjøres aldri, fordi return avslutter funksjonen.

✏️Eksempel 2: en funksjon som gir to svar

Skriv ytterpunkter(maalinger) som returnerer både den laveste og den høyeste verdien i ei liste. Vis begge måtene å ta imot svaret på.

De to verdiene pakkes i ett tuppel på veien ut.

def ytterpunkter(maalinger):
    return min(maalinger), max(maalinger)

grader = [4, -2, 9, 3, 7]
lav, hoy = ytterpunkter(grader)
print(lav, hoy)
svar = ytterpunkter(grader)
print(svar)
print(svar[1])

Utskrift:

-2 9
(-2, 9)
9

De to måtene gir samme informasjon. Utpakkingen lav, hoy = ... er den lesbare, og den løsningsforslagene bruker; å beholde tuppelet samlet er nyttig når du skal sende svaret rett videre til en annen funksjon.

Legg merke til at print(svar) viser tuppelet med parenteser og komma: (-2, 9). Det er akkurat den forskjellen en kodesporingsoppgave tester.

Samme mekanisme brukes til å bytte om to variabler:

a = 'først'
b = 'sist'
a, b = b, a
print(a, b)

Utskrift:

sist først

📝Oppgave 2
Sjanger B

Hva skriver programmet ut? Skriv alle tre linjene nøyaktig.

def del_opp(sek):
    return sek // 60, sek % 60

m, s = del_opp(143)
print(m, s)
print(del_opp(143))
x = 1
y = 2
x, y = y, x + y
print(x, y)

Løkke 3 — parallelle tupler (~12 min)

Nå kommer den formen tupler faktisk har på eksamen. Karaktersettingen trenger to opplysninger per karakter: grensen og bokstaven. De legges i to tupler av samme lengde, der samme plassnummer hører sammen:

grenser = (90, 80, 70, 60)
bokstaver = ('A', 'B', 'C', 'D')

Grense 90 hører til bokstav 'A' fordi begge står på plass 0. Dette kalles parallelle tupler, og den samme teknikken brukes til øvelser med vekter, varer med priser og soner med satser. Du kunne brukt en ordbok, men parallelle tupler bevarer rekkefølgen, og karaktersetting handler nettopp om å gå gjennom grensene fra høyest til lavest.

Parallelle tupler

To eller flere samlinger av samme lengde, der samme plassnummer hører sammen. grenser[2] og bokstaver[2] er to opplysninger om den samme karakteren.

Du går gjennom dem med én felles indeks: for i in range(len(grenser)): og bruker grenser[i] og bokstaver[i] inni. Forutsetningen er at de er like lange og i samme rekkefølge — går de ut av takt, blir alt feil uten at noe krasjer, og det er den farligste feilen i mønsteret.

`zip(a, b)`

Kobler to samlinger element for element og gir parene som tupler: zip((90, 80), ('A', 'B')) gir parene (90, 'A') og (80, 'B').

Den brukes i løkkehodet med utpakking: for grense, bokstav in zip(grenser, bokstaver): — da slipper du indeksen helt. Er samlingene ulikt lange, stopper zip ved den korteste, uten feilmelding. Skal du se resultatet direkte, må du konvertere: list(zip(a, b)).

📜Kodemønster: karaktersetting med parallelle tupler
Problemet: en poengsum skal oversettes til en kategori — karakter, prisklasse, sone, aldersgruppe.

Mønsteret, i fire steg:

1. Legg grensene i ett tuppel, sortert fra høyest til lavest, og kategoriene i et parallelt tuppel.
2. Gå gjennom dem med felles indeks eller med zip.
3. Returnér den første kategorien der poengsummen er stor nok — fordi grensene er sortert synkende, er den første treffen alltid den riktige.
4. Etter løkka: returnér bunnkategorien, den som gjelder når ingen grense ble nådd.

Steg 4 er det som glipper oftest. Uten det returnerer funksjonen None for alle poengsummer under den laveste grensen, og da er både 'F' og feilmeldingen borte.

Merk at return inne i løkka avslutter funksjonen med én gang — du trenger ingen break og ingen hjelpevariabel.

✏️Eksempel 3: fra poengsum til karakter

Med grenser = (90, 80, 70, 60) og bokstaver = ('A', 'B', 'C', 'D'): skriv karakter(poeng) som gir riktig bokstav, og 'F' når ingen grense er nådd. Test med 95, 82, 60 og 41.

Først med felles indeks:

grenser = (90, 80, 70, 60)
bokstaver = ('A', 'B', 'C', 'D')

def karakter(poeng):
    for i in range(len(grenser)):
        if poeng >= grenser[i]:
            return bokstaver[i]
    return 'F'

for p in [95, 82, 60, 41]:
    print(p, karakter(p))

Utskrift:

95 A
82 B
60 D
41 F

Og her er den samme funksjonen med zip, som fjerner indeksen:

grenser = (90, 80, 70, 60)
bokstaver = ('A', 'B', 'C', 'D')

def karakter(poeng):
    for grense, bokstav in zip(grenser, bokstaver):
        if poeng >= grense:
            return bokstav
    return 'F'

for p in [95, 82, 60, 41]:
    print(p, karakter(p))

Utskrift:

95 A
82 B
60 D
41 F

Begge gir full pott. Indeksvarianten er den du trenger når du skal se på nabogrensen også; zip-varianten er kortere og vanskeligere å bomme på.

Tre detaljer avgjør om funksjonen er riktig. Grensene må stå synkende — står de stigende, får alle over 60 poeng en 'D', fordi den første treffen da er feil. Sammenligningen må være >= og ikke >, ellers får den som har akkurat 60 poeng en 'F'. Og return 'F' må stå etter løkka, på funksjonens innrykksnivå — legger du den inne i løkka, avsluttes funksjonen allerede på første grense som ikke er nådd.

Merk til slutt at 60 poeng gir 'D' og ikke 'F': grensen er «minst 60», og det er akkurat kanttilfellet en oppgave liker å teste.

📝Oppgave 3
Sjanger G

En kommune deler vannforbruk i prisklasser. Grensene i kubikkmeter er (300, 200, 120, 60) og de tilhørende klassene er ('rød', 'oransje', 'gul', 'grønn'). Alt under den laveste grensen er klasse 'blå'.

a) Skriv klasse(forbruk) som returnerer riktig klassenavn.
b) Skriv klassefordeling(forbrukstall) som tar ei liste med forbrukstall og returnerer en ordbok fra klassenavn til antall husstander i den klassen. Funksjonen skal gjenbruke klasse.

Test med forbrukstall = [340, 55, 210, 120, 61, 299, 15].

📝Oppgave 4
Sjanger B

Hva skriver programmet ut? Skriv alle tre linjene nøyaktig.

navn = ('nord', 'sør', 'øst', 'vest')
tall = (12, 7, 9)
for n, x in zip(navn, tall):
    print(n, x)
print(len(list(zip(navn, tall))))

Tuppel som ordboknøkkel

Fordi tupler er uforanderlige, kan de brukes som nøkler i en ordbok — noe ei liste ikke kan. Det gir deg en sammensatt nøkkel: d[('nord', 'papir')] = 200 slår opp på sone og fraksjon samtidig.

Bruken er sjelden på eksamen, men verdt å kjenne igjen: ser du firkantparenteser med et tuppel inni, er det ett oppslag på to opplysninger, ikke to oppslag etter hverandre. Alternativet er en ordbok inne i en ordbok.

📝Oppgave 5
Eksamensnivå, sjanger G

En femkamp har fem øvelser med hver sin vekt. Øvelsesnavnene ligger i ovelser, vektene i vekter, og hver deltakers poeng ligger som ei liste i samme rekkefølge som øvelsene.

a) Skriv totalpoeng(poeng, vekter) som returnerer den vektede summen, altså hvert poengtall ganget med sin vekt og lagt sammen.
b) Skriv beste_ovelse(poeng, ovelser) som returnerer et tuppel med navnet på øvelsen deltakeren fikk flest poeng i, og selve poengsummen. Returnér None hvis poenglista er tom.
c) Skriv rangering(navn, alle_poeng, vekter) som returnerer en ordbok fra deltakernavn til totalpoeng.

Test med ovelser = ('sprint', 'lengde', 'kule', 'høyde', 'løp'), vekter = (1.0, 1.2, 0.8, 1.1, 1.5), navn = ['Rein', 'Sand'] og alle_poeng = [[8, 6, 9, 5, 7], [7, 9, 4, 8, 6]].

📝Oppgave 6
Eksamensnivå, sjanger B

Hva skriver programmet ut? Skriv alle fire linjene nøyaktig, med de parentesene Python faktisk bruker.

par = ('sone', 4)
a, b = par
print(a, b)
print(par[0][1:])
ny = par + ('rest',)
print(ny)
print(len(ny), type(ny) == tuple)

Repetisjon — de seks setningene du bør ha med deg
API- og konstruksjonsliste

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 Norges teknisk-naturvitenskapelige universitet. Dette er ikke offisielt studiemateriell. Les mer.