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.
begrepet flest studenter bommer på. Det bærer Oppgave 2, flervalget om objekter, som er rundt ni poeng fordelt på fire spørsmål.
Sjangeren er B — flervalg om objekter, altså «hvilken påstand om denne koden er sann?» eller «hva skrives ut?». Formen er alltid den samme: én kodesnutt med to klasser, noen tilordninger mellom objektene, og fire spørsmål om hva som peker på hva akkurat da.
Dette må sitte — bokas høyeste prioritetsnivå. Det er ikke fordi stoffet er
mye, men fordi det er lett å tro at man har forstått det. To konkrete varianter går igjen i settene vi har talt: at trener = far gir to navn på samme
objekt, og at en byttemetode endrer koblingene mellom objekter uten å endre objektene selv.
Blankt svar gir null poeng, akkurat som et galt svar. Kryss alltid av — også
når du er usikker.
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 02. 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.
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.
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 1Sammenlign 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.
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.
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 100Tegn de to tilfellene, og forskjellen blir tydelig. I det første peker a ogb 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.
(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.
Å 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.
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 9Fø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.
Feilkode #2 er den dyreste i Oppgave 2, og den har to ansikter.
Ansikt 1: å tro at b = a lager en kopi. Det gjør den ikke, hverken for
lister eller objekter. Endrer du gjennom a, ser du det gjennom b.
Ansikt 2: å tro at det gamle objektet er borte etter en rebinding. Det er det
bare hvis ingen andre peker på det. Så lenge én variabel — eller én liste, eller én instansvariabel — holder på det, lever det videre uendret.
Den lumske kombinasjonen ser slik ut:
a = Boks(1)
b = a a = Boks(5)Etter disse tre linjene finnes to objekter. a peker på det nye med 5, b peker fortsatt på det gamle med 1. Blokken er merket som ikke-kjørbar fordi den er et utdrag uten klassen rundt.
Mange leser den tredje linjen som «a og dermed b får verdien 5». Det er galt, og det er akkurat den lesefeilen spørsmålene er laget for å avsløre.
Motgiften er tegningen. Tegn en pil du kan flytte, og en boks du kan skrive
i. Da kan du ikke blande dem.
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 PerDen første funksjonen gjør ingenting synlig. Parameterne p og q er
lokale navn inne i funksjonen. De peker riktignok på de samme objektene somper 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 —
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)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.
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 LovelaceKontoen fikk aldri beskjed om navneendringen — den peker på eier-objektet, og
leser navnet på nytt hver gang. Hadde konstruktøren i stedet lagretself._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.
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 1Legg 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.
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 1alle[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.
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))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 = nya) ada = Eier("Ada"), k = Konto(ada), ada.sett_navn("Nora"). Hva girk.hent_eier().hent_navn()?
b) ada = Eier("Ada"), k = Konto(ada), ada = Eier("Nora"). Hva girk.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.
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 ruDerfor 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.
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 1Fø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.
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 5Sporingstavle over hvem som peker på hva:
| Linje | ada peker på | lag._leder peker på | lag._medlemmer |
|---|---|---|---|
ada = Spiller("Ada") | boks A | — | — |
lag = Lag(ada) | boks A | boks A | boks A |
lag.ta_opp(nora) | boks A | boks A | boks 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 C | boks A | boks 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 100poengene 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 spillerenshent_poeng — etter navneregelen for containere fraHva 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))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?
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 skrevetbok = erstatt(bok), ville boka hett erstattet.»
Fem feil koster nesten alle poengene som mistes i Oppgave 2:
- #2 — b = a lest som en kopi. Det er én ting med to navn. Endring gjennom
det ene sees gjennom det andre.
- #2 — tro at det gamle objektet er borte etter en rebinding. Det lever
videre så lenge noe peker på det: en variabel, en liste eller en instansvariabel.
- #12 — tro at en funksjon kan flytte kallerens pil. Parameteren er et lokalt
navn. Funksjonen kan endre objektet, ikke hvilket objekt kalleren peker på.
- Telle referanser i stedet for objekter. En liste med fire elementer kan
holde tre objekter, eller ett. Tell hvor mange ganger klassenavnet står med parentes.
- Lagre rådata i stedet for en objektreferanse. `self.eiernavn =
eier.hent_navn()` blir foreldet i det øyeblikket eieren skifter navn.
Og én vane som løser alt: tegn boksene og pilene. Tilordning mellom to variabler flytter en pil; et metodekall skriver i en boks.
Begrepsbank
Begrepsbanken er flashcard- og repetisjonsstoff — den gjentar det du nettopp har lest. Hopp trygt over ved førstegangslesing; tidsanslaget for kapitlet gjelder kjernestoffet.
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.
To eller flere navn på det samme objektet. Oppstår av b = a, av enappend(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.
Å 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.
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.
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.
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.
self._eier = person lar objektet holde et annet objekt.Fordelen er at modellen holder seg i takt: leser du navnet medself._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.
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.
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.
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.
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.
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».
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.
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.
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.
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.
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.
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:.
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.
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.
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.
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.