5.4 Referansesemantikk

At to variabler kan være to navn på **samme objekt**, at objekter kan holde referanser til andre objekter, og at rebinding endrer koblinger uten å røre objektene selv — det vanskeligste begrepet i Oppgave 2.

55 min
8 oppgaver
Referansesemantikk
Din fremgang i kapitlet
0 / 8 oppgaver

Forkunnskaper — sist du var her

Kapitlet bygger på kap. 5.1 — Klasser og objekter,
kap. 5.2 — Innkapsling og
kap. 5.3 — __str__, og henter én ting fra
kap. 3.1 — Lister.

De to tingene du må ha klart, friskes opp med kjørt kode.

1. En metode virker bare på det objektet den ble kalt på:

class Kurv:
    def __init__(self):
        self._antall = 0

    def legg_i(self):
        self._antall = self._antall + 1

    def hent(self):
        return self._antall

a = Kurv()
b = Kurv()
a.legg_i()
print(a.hent(), b.hent())

Utskrift:

1 0

2. Fra lista-kapitlet: b = a på to lister gir to navn på samme liste. Det
er feilkode #15, aliasing:

a = [1, 2, 3]
b = a b.append(4)
print(a, b)

Utskrift:

[1, 2, 3, 4] [1, 2, 3, 4]

Det siste er nøkkelen til hele kapitlet. Det som gjelder for lister, gjelder for alle objekter — og fra nå av er det objekter det handler om.

Løkke 1 — Hva en variabel egentlig holder (~13 min)

En sykkelutleie har én sykkel med kode R31. To ansatte kaller den to ting: den ene sier «reparasjonssykkelen», den andre sier «sykkel nummer 31».

Det er én sykkel med to navn. Skrur den ene ansatte på den, ser den andre
det, for det er den samme fysiske sykkelen.

Sånn er det også i Python. En variabel «inneholder» ikke objektet — den peker på det. Og to variabler kan peke på det samme.

Referanse

Det en variabel faktisk holder når den «inneholder» et objekt: en peker til hvor objektet ligger, ikke objektet selv.

Bildet som hjelper: objektet er et hus, variabelen er en lapp med adressen.
Kopierer du lappen, får du to lapper til samme hus — ikke to hus.

Konsekvensen ser du med én gang du endrer noe. Går du inn i huset via den ene lappen og maler stua, er stua malt også når du kommer inn via den andre.

`b = a` gir to navn på samme objekt

Tilordningen kopierer referansen, ikke objektet. Etterpå peker begge variablene på den samme tingen:

class Kurv:
    def __init__(self):
        self._antall = 0

    def legg_i(self):
        self._antall = self._antall + 1

    def hent(self):
        return self._antall

a = Kurv()
b = a a.legg_i()
print(a.hent(), b.hent())

Utskrift:

1 1

Sammenlign med forkunnskapsboksen, der b = Kurv() laget et nytt objekt og svaret ble 1 0. Forskjellen er én linje, og den avgjør alt.

Regelen i én setning: Klassenavn(...) lager et nytt objekt, = mellom to
variabler gjør det ikke.

Å tegne objekter og piler

Framgangsmåten som løser hvert eneste flervalg om referanser: tegn objektene som bokser og variablene som piler inn i dem.

En tilordning b = a tegnes ved å la b-pilen peke der a-pilen peker. En tilordning b = Kurv() tegnes ved å lage en ny boks og la b peke på den.

Et metodekall som endrer en verdi, tegnes ved å endre tallet inne i boksen — pilene rører du ikke.

Tre tegn er nok: boks, pil, navn. Bruk to minutter på tegningen før du svarer, så faller de fleste spørsmålene av seg selv.

✏️Eksempel 1: To navn, ett objekt

Vis forskjellen på å lage to objekter og å lage to navn på ett, med samme klasse og nesten samme kode.

Klassen er den samme i begge tilfellene. Bare den ene linjen midt i skiller dem:

class Konto:
    def __init__(self, eier):
        self._eier = eier self._saldo = 0

    def sett_inn(self, belop):
        self._saldo = self._saldo + belop

    def hent_saldo(self):
        return self._saldo

# Tilfelle 1: to objekter
a = Konto("Ada")
b = Konto("Ada")
a.sett_inn(100)
print("to objekter:", a.hent_saldo(), b.hent_saldo())

# Tilfelle 2: to navn paa ett objekt
c = Konto("Nora")
d = c c.sett_inn(100)
print("ett objekt: ", c.hent_saldo(), d.hent_saldo())

Utskrift:

to objekter: 100 0 ett objekt:  100 100

Tegn de to tilfellene, og forskjellen blir tydelig. I det første peker a og
b på hver sin boks. I det andre peker c og d på den samme, og det finnes bare én saldo å endre.

Legg merke til at Konto("Ada") to ganger gir to forskjellige objekter, selv
om eieren heter det samme. Like verdier gjør ikke to objekter til ett — de er laget hver for seg, og de lever hver for seg.

Slik ville sensor sett på det: dette er kjernen i Oppgave 2, og spørsmålet
stilles nesten alltid som «hva skrives ut». Klarer du å tegne pilene riktig, er poengene dine.

Merk kommentarene i koden. De er skrevet uten æ, ø og å, som all kode i denne boka, og de brukes her fordi de forklarer en forutsetning — ikke for å gjenta hva koden gjør.

📝Oppgave 1

(Innstegsoppgave — minimal sporing.) Klassen Teller har self._n = 0 i konstruktøren, metoden opp() som øker self._n med 1, og hent() som returnerer den.

a) Hva skriver dette ut?

x = Teller()
y = Teller()
x.opp()
print(x.hent(), y.hent())

b) Hva skriver dette ut?

x = Teller()
y = x x.opp()
print(x.hent(), y.hent())

c) Hva er den eneste forskjellen mellom de to programmene?

Løkke 2 — Rebinding: å flytte en pil (~14 min)

Nå kommer den delen flest bommer på.

Å tilordne på nytt — x = nytt_objekt — endrer ikke objektet x pekte på før. Det flytter bare pilen. Det gamle objektet lever videre hvis noe annet peker på det, og forsvinner ellers stille ut av programmet.

Ordet for dette er rebinding, og det er hovedpersonen i objekt-flervalget.

Rebinding

Å la en variabel peke på noe annet enn før: x = nytt_objekt.

Tre ting skjer, og det er den midterste folk overser:

- pilen fra x flyttes til det nye objektet,
- det gamle objektet endres ikke i det hele tatt,
- det gamle objektet lever videre hvis en annen variabel fortsatt peker på det.

Skillet er verdt å si høyt: x.sett_navn("Ada") endrer objektet;
x = Person("Ada") endrer pilen. Det første sees av alle som peker på objektet; det andre sees bare av x.

Å endre objektet mot å flytte pilen

De to tingene som kan skje i en tilordning, og som avgjør hvert eneste flervalgsspørsmål:

class Boks:
    def __init__(self, tall):
        self._tall = tall

    def sett(self, nytt):
        self._tall = nytt

    def hent(self):
        return self._tall

a = Boks(1)
b = a a.sett(9)
print(a.hent(), b.hent())

a = Boks(5)
print(a.hent(), b.hent())

Utskrift:

9 9
5 9

Første print: a.sett(9) endret objektet, og begge navnene ser det.

Andre print: a = Boks(5) flyttet pilen fra a til et nytt objekt. b peker fortsatt på det gamle, som fremdeles har 9 i seg. Ingen verdi ble overskrevet — de to navnene peker bare ikke lenger på det samme.

✏️Eksempel 2: Byttemetoden — det klassiske eksamensmønsteret

En funksjon får inn to objekter og «bytter» dem. Vis hva som faktisk skjer, og hvorfor objektene utenfor funksjonen er uendret.

Dette mønsteret går igjen i settene, og det tester to ting samtidig:
referansesemantikk og scope fra kap. 2.2.

class Person:
    def __init__(self, navn):
        self._navn = navn

    def sett_navn(self, nytt):
        self._navn = nytt

    def __str__(self):
        return self._navn

def bytt_pilene(p, q):
    hjelp = p p = q q = hjelp

def bytt_navnene(p, q):
    hjelp = str(p)
    p.sett_navn(str(q))
    q.sett_navn(hjelp)

per = Person("Per")
palle = Person("Palle")

bytt_pilene(per, palle)
print(per, palle)

bytt_navnene(per, palle)
print(per, palle)

Utskrift:

Per Palle
Palle Per

Den første funksjonen gjør ingenting synlig. Parameterne p og q er
lokale navn inne i funksjonen. De peker riktignok på de samme objektene som
per og palle, men å flytte de lokale pilene rører ikke pilene utenfor. Det er feilkode #12, scope-forvirring, kombinert med #2.

Den andre funksjonen virker, fordi den endrer objektene i stedet for
pilene. p.sett_navn(...) går inn i boksen og skriver om innholdet, og den boksen er den samme som per peker på.

Regelen som følger av dette: en funksjon kan endre et objekt den får inn, men
den kan ikke endre hvilke objekter kalleren sine variabler peker på.

Slik ville sensor sett på det: dette er et fast spørsmål i Oppgave 2, ofte
formulert som «hva skrives ut etter kallet på bytt?». Klarer du å si at pilene er lokale mens objektene er delte, har du hele poenget.

— naturlig pausepunkt —

📝Oppgave 2
Eksamensnivå, sjanger B

Hva skriver programmet ut?

class Merke:
    def __init__(self, tekst):
        self._tekst = tekst

    def sett(self, ny):
        self._tekst = ny

    def __str__(self):
        return self._tekst

a = Merke("rod")
b = a a.sett("blaa")
print(a, b)

a = Merke("gronn")
print(a, b)

b.sett("gul")
print(a, b)

📝Oppgave 3
Eksamensnivå, sjanger B

Se på koden:

class Kort:
    def __init__(self, poeng):
        self._poeng = poeng

    def gi(self, n):
        self._poeng = self._poeng + n

    def hent(self):
        return self._poeng

def doble(k):
    k.gi(k.hent())

def nullstill(k):
    k = Kort(0)

kort = Kort(10)
doble(kort)
print(kort.hent())
nullstill(kort)
print(kort.hent())

a) Hva skriver programmet ut?

b) Hvorfor virker doble, men ikke nullstill?

c) Skriv om nullstill slik at den virker.

Løkke 3 — Objekter som peker på objekter (~14 min)

Referanser blir for alvor interessante når et objekt holder en referanse til et annet.

En konto har en eier. En sykkel står i et stativ. Et medlem har en fastlege. I alle tilfellene er det naturlige valget å la instansvariabelen peke på et helt objekt, ikke på en kopi av opplysningene — og det er nettopp formen den store oppgaven bruker.

Objektreferanse som instansvariabel

En instansvariabel kan peke på et annet objekt, akkurat som en vanlig variabel:

class Eier:
    def __init__(self, navn):
        self._navn = navn

    def hent_navn(self):
        return self._navn

    def sett_navn(self, nytt):
        self._navn = nytt

class Konto:
    def __init__(self, eier):
        self._eier = eier self._saldo = 0

    def hent_eier_navn(self):
        return self._eier.hent_navn()

ada = Eier("Ada")
k = Konto(ada)
print(k.hent_eier_navn())
ada.sett_navn("Ada Lovelace")
print(k.hent_eier_navn())

Utskrift:

Ada
Ada Lovelace

Kontoen fikk aldri beskjed om navneendringen — den peker på eier-objektet, og
leser navnet på nytt hver gang. Hadde konstruktøren i stedet lagret
self._eier_navn = eier.hent_navn(), ville kontoen hatt et gammelt navn liggende etter endringen.

Det er hele argumentet for å lagre objektreferanser og ikke rådata: modellen
holder seg i takt av seg selv.

En-til-mange som samling av referanser

Skal ett objekt holde mange andre, legges referansene i en liste eller en ordbok:

class Bok:
    def __init__(self, tittel):
        self._tittel = tittel

    def __str__(self):
        return self._tittel

class Hylle:
    def __init__(self):
        self._boker = []

    def sett_inn(self, b):
        self._boker.append(b)

    def antall(self):
        return len(self._boker)

sult = Bok("Sult")
h1 = Hylle()
h2 = Hylle()
h1.sett_inn(sult)
h2.sett_inn(sult)
print(h1.antall(), h2.antall())

Utskrift:

1 1

Legg merke til at det bare finnes én bok. Den ligger i to hyller samtidig,
fordi begge listene holder en referanse til det samme objektet. Endres boka, endres den begge steder.

Om det er ønsket eller ikke, avhenger av oppgaven. Poenget er at du vet at det er slik det virker.

✏️Eksempel 3: Når to samlinger deler objekter

Vis hva som skjer når det samme objektet ligger i to samlinger, og én av dem endrer det.

Et treningssenter har to lister: alle medlemmer, og de som venter på plass.

class Medlem:
    def __init__(self, navn):
        self._navn = navn self._timer = 0

    def tren(self, t):
        self._timer = self._timer + t

    def __str__(self):
        return self._navn + " (" + str(self._timer) + " t)"

ada = Medlem("Ada")
nora = Medlem("Nora")

alle = []
alle.append(ada)
alle.append(nora)

venteliste = []
venteliste.append(ada)

alle[0].tren(3)

print(alle[0], alle[1])
print(venteliste[0])
print(len(alle), len(venteliste))

Utskrift:

Ada (3 t) Nora (0 t)
Ada (3 t)
2 1

alle[0] og venteliste[0] er det samme objektet. Treningstimene ble lagt
til gjennom den ene lista og sees gjennom den andre. Ingen kopiering skjedde noen gang.

Prøv motsatt tanke: hadde du villet at ventelista skulle ha sin egen kopi,
måtte du laget et nytt objekt med Medlem("Ada"). Det ville gitt to Ada-er som ikke visste om hverandre — nesten alltid feil i en modell.

Slik ville sensor sett på det: dette er et vanlig flervalgsspørsmål i formen
«hvor mange objekter finnes etter denne koden?». Svaret her er to medlemmer, ikke tre, selv om det står tre navn i to lister.

Legg merke til at len(alle) og len(venteliste) teller referanser, ikke objekter. To lister kan til sammen ha fem referanser til ett eneste objekt.

📝Oppgave 4
Eksamensnivå, sjanger B

Hva skriver programmet ut?

class Sykkel:
    def __init__(self, kode):
        self._kode = kode self._stativ = "ukjent"

    def sett_stativ(self, s):
        self._stativ = s

    def __str__(self):
        return self._kode + "@" + self._stativ

s1 = Sykkel("R31")
liste = []
liste.append(s1)
liste.append(s1)
liste[0].sett_stativ("Blindern")
print(liste[0], liste[1], len(liste))

s1 = Sykkel("R48")
print(liste[0], s1, len(liste))

📝Oppgave 5
Eksamensnivå, sjanger B

Se på koden:

class Eier:
    def __init__(self, navn):
        self._navn = navn

    def hent_navn(self):
        return self._navn

    def sett_navn(self, n):
        self._navn = n

class Konto:
    def __init__(self, eier):
        self._eier = eier

    def hent_eier(self):
        return self._eier

    def sett_eier(self, ny):
        self._eier = ny

a) ada = Eier("Ada"), k = Konto(ada), ada.sett_navn("Nora"). Hva gir
k.hent_eier().hent_navn()?

b) ada = Eier("Ada"), k = Konto(ada), ada = Eier("Nora"). Hva gir
k.hent_eier().hent_navn() nå?

c) Forklar forskjellen med én setning.

Løkke 4 — Hvorfor tall og tekster ser annerledes ut (~12 min)

Alt i Python er objekter, også tallet 5. Hvorfor merker du da ikke referansesemantikken når du regner?

Fordi tall og tekster er uforanderlige. De kan ikke endres etter at de er laget, så det finnes ingen måte å se at to navn peker på det samme. Enhver
«endring» lager i praksis en ny verdi.

Uforanderlige verdier

Tall, tekster og sannhetsverdier kan ikke endres etter at de er laget.

x = x + 1 endrer ikke tallet — det lager tallet 6 og lar x peke på det. Det er en rebinding, ikke en endring.

a = 5 b = a a = a + 1 print(a, b)

s = "ru" u = s s = s + "te" print(s, u)

Utskrift:

6 5 rute ru

Derfor oppfører tall og tekster seg «som verdier» i praksis, selv om de teknisk sett er objekter. Lister, ordbøker og dine egne klasser kan derimot endres, og da blir delingen synlig.

Kort huskeregel: kan du endre den, merker du referansen. Kan du ikke, merker du den aldri.

Hvorfor det er de foranderlige som feller deg

Sammenlign de to tilfellene, med samme form på koden:

class Kurv:
    def __init__(self):
        self._n = 0

    def opp(self):
        self._n = self._n + 1

    def hent(self):
        return self._n

a = 5 b = a a = a + 1 print("tall:  ", a, b)

liste_a = [1]
liste_b = liste_a liste_a.append(2)
print("liste: ", liste_a, liste_b)

k1 = Kurv()
k2 = k1 k1.opp()
print("objekt:", k1.hent(), k2.hent())

Utskrift:

tall:   6 5 liste:  [1, 2] [1, 2]
objekt: 1 1

Første linje: tallene skiller lag. a = a + 1 er en rebinding.

Andre og tredje linje: lista og objektet følges av. append og opp endrer
tingen selv, og begge navnene ser det.

Det er nettopp fordi tall oppfører seg «snilt» at objektene overrasker. Ta med deg mistanken: er verdien noe som kan endres, er delingen synlig.

✏️Eksempel 4: Full Oppgave 2-snutt, sporet steg for steg

En komplett to-klasse-snutt av den typen som kommer i Oppgave 2, med fire spørsmål og full sporing.

Snutten under er skrevet i samme form som eksamensoppgaven, med en brettspillklubb som innpakning:

class Spiller:
    def __init__(self, navn):
        self._navn = navn self._poeng = 0

    def gi_poeng(self, n):
        self._poeng = self._poeng + n

    def hent_poeng(self):
        return self._poeng

    def __str__(self):
        return self._navn + ":" + str(self._poeng)

class Lag:
    def __init__(self, leder):
        self._leder = leder self._medlemmer = []
        self._medlemmer.append(leder)

    def ta_opp(self, s):
        self._medlemmer.append(s)

    def hent_leder(self):
        return self._leder

    def totalt_antall_poeng(self):
        sum_poeng = 0 for m in self._medlemmer:
            sum_poeng = sum_poeng + m.hent_poeng()
        return sum_poeng

ada = Spiller("Ada")
nora = Spiller("Nora")
lag = Lag(ada)
lag.ta_opp(nora)

ada.gi_poeng(5)
print(lag.hent_leder(), lag.totalt_antall_poeng())

ada = Spiller("Ada2")
ada.gi_poeng(100)
print(lag.hent_leder(), lag.totalt_antall_poeng())

Utskrift:

Ada:5 5
Ada:5 5

Sporingstavle over hvem som peker på hva:

Linjeada peker pålag._leder peker pålag._medlemmer
ada = Spiller("Ada")boks A
lag = Lag(ada)boks Aboks Aboks A
lag.ta_opp(nora)boks Aboks Aboks A, boks N
ada.gi_poeng(5)boks A (5 p)boks A (5 p)boks A (5 p), boks N
ada = Spiller("Ada2")boks Cboks Aboks A, boks N
ada.gi_poeng(100)boks C (100 p)boks A (5 p)boks A, boks N

Fellen er de to siste linjene. Rebindingen ga ada en ny boks, og de 100
poengene havnet der. Laget peker fortsatt på den gamle boksen, så både lederen og summen står uendret på 5.
Slik ville sensor sett på det: dette er hele Oppgave 2, som er rundt ni
poeng. Fire spørsmål stilles typisk til en slik snutt: hva er lederens navn etter rebindingen, hva er totalsummen, hvor mange Spiller-objekter finnes (svar:
tre), og er _medlemmer del av grensesnittet (svar: nei).
Legg merke til at totalt_antall_poeng heter noe annet enn spillerens
hent_poeng — etter navneregelen for containere fra

kap. 5.3.

📝Oppgave 6
Eksamensnivå, sjanger A

Hva skriver programmet ut?

class Post:
    def __init__(self, tekst):
        self._tekst = tekst

    def __str__(self):
        return self._tekst

a = Post("en")
b = Post("to")
liste = []
liste.append(a)
liste.append(b)
liste.append(a)

a = Post("tre")
liste.append(a)

for p in liste:
    print(p)
print(len(liste))

📝Oppgave 7
Eksamensnivå, sjanger B

Se på koden:

class Sal:
    def __init__(self, navn):
        self._navn = navn self._paameldte = 0

    def meld_paa(self, n):
        self._paameldte = self._paameldte + n

    def hent_paameldte(self):
        return self._paameldte

    def __str__(self):
        return self._navn + "(" + str(self._paameldte) + ")"

class Senter:
    def __init__(self):
        self._saler = {}

    def legg_til(self, navn, sal):
        self._saler[navn] = sal

    def hent(self, navn):
        if navn in self._saler:
            return self._saler[navn]
        return None

speil = Sal("Speilsal")
s = Senter()
s.legg_til("speil", speil)
s.legg_til("kopi", speil)

speil.meld_paa(4)
print(s.hent("speil"), s.hent("kopi"))

speil = Sal("Speilsal")
print(s.hent("speil"), speil)
print(s.hent("basseng"))

a) Hva skriver programmet ut?

b) Hvor mange Sal-objekter lages i programmet?

c) Hva ville s.hent("basseng") gjort hvis if navn in self._saler:-sjekken
manglet?

📝Oppgave 8
Eksamensnivå, sjanger B

Ta stilling til hver påstand og begrunn kort.

class Bok:
    def __init__(self, tittel):
        self._tittel = tittel

    def sett_tittel(self, t):
        self._tittel = t

    def __str__(self):
        return self._tittel

def endre(b):
    b.sett_tittel("endret")

def erstatt(b):
    b = Bok("erstattet")

a) «Etter endre(bok) heter boka endret

b) «Etter erstatt(bok) heter boka erstattet

c) «Hvis erstatt hadde hatt return b til slutt, og kalleren hadde skrevet
bok = erstatt(bok), ville boka hett erstattet

API- og konstruksjonsliste

Begrepsbank

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

Referansesemantikk

Regelen om at en variabel holder en peker til et objekt, ikke objektet selv.

Konsekvensen er den ene setningen hele kapitlet handler om: to variabler kan peke på det samme, og da sees enhver endring begge steder.

Ordet «semantikk» betyr her bare «hva tilordningen betyr». Alternativet, som noen språk har for enkelte typer, kalles verdisemantikk: der lager b = a en faktisk kopi. Python gjør aldri det av seg selv.

Alias

To eller flere navn på det samme objektet. Oppstår av b = a, av en
append(a), av et funksjonskall f(a) og av self._x = a.

Alias er ikke en feil — det er ofte nettopp det du vil ha, som når det samme medlemmet ligger i både medlemslista og ventelista.

Feilen er å glemme at det er et alias. Det er feilkode #15 for lister, og feilkode #2 for objekter — samme misforståelse i to drakter.

Rebinding

Å flytte en variabel til å peke på noe annet: x = nytt_objekt.

Det som ikke skjer: det gamle objektet endres ikke, og andre variabler som peker på det, merker ingenting.

Kjenn den igjen i koden på at det står et klassenavn med parentes, eller et annet variabelnavn, på høyre side av likhetstegnet — ikke et metodekall på venstre.

Foranderlig og uforanderlig
Uforanderlige: tall, tekster og sannhetsverdier. De kan ikke endres etter at
de er laget, så delingen sees aldri.

Foranderlige: lister, ordbøker og dine egne klasser. De kan endres, og da blir
delingen synlig.

Huskeregelen: er verdien noe du kan endre, må du tenke på hvem andre som peker på den. Er den ikke det, kan du la være.

Å telle objekter

Antall objekter er antall ganger et klassenavn står med parentes — ikke antall variabler, og ikke antall elementer i en liste.

Fire referanser i en liste kan være fire objekter, tre, eller ett.

Spørsmålet «hvor mange objekter er laget?» er et vanlig flervalg i Oppgave 2, og det tar ti sekunder når du vet hva du skal telle.

Å tegne bokser og piler

Metoden som løser hvert eneste referansespørsmål. En boks per objekt, en pil per variabel eller instansvariabel.

- b = a: la b-pilen peke der a-pilen peker.
- b = Klasse(): tegn en ny boks, la b peke på den.
- a.sett_x(5): skriv 5 inn i boksen a peker på. Rør ingen piler.

Bruk to minutter på tegningen før du svarer. Det er raskere enn å lese koden tre ganger.

Objektreferanse i en instansvariabel
self._eier = person lar objektet holde et annet objekt.

Fordelen er at modellen holder seg i takt: leser du navnet med
self._eier.hent_navn(), får du alltid det gjeldende navnet.

Alternativet — å lagre self._eier_navn = person.hent_navn() i konstruktøren — gir en verdi som blir foreldet i det øyeblikket personen skifter navn. Det er en av de vanligste modelleringsfeilene i den store oppgaven.

Lenket metodekall
konto.hent_eier().hent_navn() — første metode gir et objekt, andre kalles på det.

Les det fra venstre: «hent eieren til kontoen, og spør den om navnet». Hvert punktum er ett steg videre.

Formen er ryddig så lenge kjeden er kort, og den er vanlig i fasiten. Blir den lang, sett resultatet i en hjelpevariabel underveis — det er lettere å spore, og det gir like god uttelling.

Parameter er et lokalt navn

Når du kaller f(a), får funksjonen sin egen pil til det samme objektet.

Funksjonen kan derfor endre objektet — det sees av kalleren. Men den kan ikke
flytte kallerens pil: en tilordning inne i funksjonen treffer bare det lokale
navnet.

Skal funksjonen gi kalleren noe nytt å peke på, må den return-ere det, og kalleren må tilordne det selv.

To samlinger som deler objekter

Legges det samme objektet i to lister eller under to nøkler, finnes det fortsatt bare ett objekt.

Endring gjennom den ene samlingen sees gjennom den andre. len() teller referanser, ikke objekter.

Er det ønsket, er det en fordel: medlemslista og ventelista holder seg i takt av seg selv. Er det ikke ønsket, må du lage et nytt objekt med klassenavnet og parentes.

Objektet som lever videre

Et objekt forsvinner ikke når én variabel slutter å peke på det. Det lever så lenge noe peker på det.

Etter a = Boks(1), b = a, a = Boks(5) finnes to objekter: det nye som a peker på, og det gamle som b fortsatt holder.

Peker ingenting lenger på et objekt, rydder Python det bort av seg selv. Det er ingenting du trenger å gjøre — men det er verdt å vite at det ikke skjedde i eksempelet over.

Byttemønsteret

Det klassiske eksamensspørsmålet: en funksjon som får inn to objekter og skal
«bytte» dem.

Bytter den pilene (hjelp = p; p = q; q = hjelp), skjer ingenting synlig utenfor — pilene er lokale.

Bytter den innholdet (leser begge verdiene og setter dem tilbake i motsatt rekkefølge med mutatorer), virker det, fordi objektene er delt.

Kommer mønsteret på eksamen, er spørsmålet nesten alltid «hva skrives ut etter kallet?», og det riktige svaret på pil-varianten er «ingenting er endret».

Feilkode #2

Referanse forvekslet med kopi. Den dyreste feilen i Oppgave 2.

Den har to former: å tro at b = a lager en kopi, og å tro at det gamle objektet er borte etter en rebinding.

Begge kureres av den samme tegningen. Tegn en boks du kan skrive i, og en pil du kan flytte — så blir det umulig å blande dem.

Feilkode #12 i objektform

Scope-forvirring: å tro at en tilordning inne i en funksjon endrer noe utenfor.

I kap. 2.2 møtte du den for tall. Her er den den samme, bare med objekter: p = Bok("ny") inne i en funksjon flytter bare det lokale navnet.

Grensen går ved punktumet. p = ... er lokalt; p.sett_x(...) treffer objektet og sees utenfor.

Hvorfor referanser er en fordel

Det kunne virket tryggere om Python kopierte alt. Det ville vært både tregt og upraktisk.

Tregt, fordi en kopi av et stort objekt koster tid og plass hver gang det sendes til en funksjon.

Upraktisk, fordi modellen din skal henge sammen: et medlem i ventelista skal
være det samme medlemmet som i medlemslista. Kopier ville gitt to versjoner som straks kom i utakt.

Identitet mot likhet

To objekter kan ha nøyaktig like verdier og likevel være to objekter.
Konto("Ada") to ganger gir to kontoer.

Identitet er «er dette det samme objektet?». Likhet er «har de samme
verdier?».

I Oppgave 2 spørres det nesten alltid om identitet. Formuleringene «peker på samme objekt», «er dette det samme» og «hvor mange objekter finnes» handler alle om det.

Referanse i en ordbok

En ordbok kan ha flere nøkler som peker på det samme objektet, akkurat som en liste kan ha flere plasser.

d["speil"] = sal og d["kopi"] = sal gir to nøkler og ett objekt. Endres salen, sees det gjennom begge.

Omvendt kan ikke én nøkkel peke på to objekter: setter du inn på en nøkkel som finnes, overskrives den forrige referansen uten en advarsel.

Å returnere et objekt

En metode kan returnere et helt objekt, ikke bare et tall eller en tekst:
return self._eier.

Den som kaller, får da en referanse til det samme objektet — ingen kopi — og kan kalle metoder på det med en gang.

Standardformen på en søkemetode er return self._d[navn] hvis nøkkelen finnes, og return None ellers. Da kan kalleren teste med if svar != None:.

Blankt svar i flervalg

Et flervalgsspørsmål som står ubesvart, gir null poeng — akkurat som et galt svar. Det er ingen straff for å gjette.

Oppgave 2 er rundt ni poeng fordelt på fire spørsmål, så et blankt felt er to poeng gitt bort gratis.

Klarer du å utelukke to av fire alternativer, er gjetningen mellom de to resterende verdt halvparten av poengene i snitt. Kryss alltid av.

Rekkefølgen på spørsmålene i Oppgave 2

Snutten er felles, og de fire spørsmålene kommer typisk i denne rekkefølgen: hva skrives ut, hva peker en gitt variabel på nå, hvor mange objekter finnes, og et begrepsspørsmål om grensesnitt eller self.

Les hele snutten og tegn før du svarer på det første. Tegningen brukes til alle fire.

Og husk at spørsmålene rettes hver for seg: en bom på det første koster ikke de tre andre.

Når rebinding er det du vil ha

Rebinding er ikke en feil — den er ofte helt riktig.

Skal en sykkel flyttes til et annet stativ, er self._stativ = nytt_stativ inne i en mutator nettopp rebinding, og den er ønsket: instansvariabelen skal peke et nytt sted.

Feilen oppstår bare når du tror at en rebinding endrer objektet i stedet for pilen. Handlingen er den samme; det er forventningen som avgjør om den er riktig eller gal.

Repetisjon — de åtte 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.