Tilbake
5.2

5.2 Innkapsling: underscore, aksessor og mutator

Innkapsling: hvorfor instansvariabler har underscore-prefiks og aksesseres gjennom `hent_x`/`sett_x`-metoder utenfra — og hva som er «del av grensesnittet».

50 min
8 oppgaver
Innkapslingunderscoreaksessormutator
Din fremgang i kapitlet
0 / 8 oppgaver

Forkunnskaper — sist du var her

Alt i dette kapitlet bygger på
kap. 5.1 — Klasser og objekter. Trenger du en oppfriskning, er dette de to tingene du må ha med deg.

1. Konstruktøren setter alt objektet skal huske, med self._ foran hver
tilordning:

class Bok:
    def __init__(self, tittel, forfatter):
        self._tittel = tittel self._forfatter = forfatter self._utlaant = False

    def skriv(self):
        print(self._tittel, "av", self._forfatter)

b = Bok("Sult", "Hamsun")
b.skriv()

Utskrift:

Sult av Hamsun

2. En metode kalles med punktum bak objektet, og self skrives aldri i
kallet:
b.skriv(), ikke b.skriv(self).

I forrige kapittel tok vi understreken for gitt og sa at forklaringen kom senere. Nå kommer den.

Løkke 1 — Hvorfor understreken står der (~11 min)

Et bibliotek har en klasse Bok med self._utlaant. Alt går bra helt til en annen del av programmet gjør dette:

b._utlaant = True

Ingen feilmelding, ingen advarsel — boka er plutselig utlånt uten at noen har lånt den, uten at utlånsdatoen er satt, og uten at antall utlån er telt opp.
Blokken er merket som ikke-kjørbar fordi den viser en arbeidsmåte boka fraråder, ikke et program vi vil kjøre.

Det er dette innkapsling beskytter mot. Objektet skal selv bestemme hvordan
verdiene sine endres, og resten av programmet skal gå gjennom metodene.

Innkapsling

Prinsippet om at et objekt skal holde verdiene sine for seg selv, og at resten av programmet bare skal kunne snakke med det gjennom metodene.

Fordelen er ikke hemmelighold — det er kontroll. Går alle endringer gjennom en metode, kan objektet passe på at reglene holdes: at saldoen ikke blir negativ, at antall påmeldte ikke overstiger kapasiteten, at to ting som hører sammen, oppdateres samtidig.

Den andre fordelen merker du når noe skal endres. Bestemmer klassen seg for å lagre alderen som fødselsår i stedet, er det bare klassens egne linjer som må skrives om — alle som kaller hent_alder(), merker ingenting.

Understrek-prefikset `_x`

Konvensjonen som sier «denne verdien hører til objektet selv — ikke rør den utenfra». Instansvariabler skrives derfor self._tittel, ikke self.tittel.

Python håndhever ingenting. Understreken er et signal til andre som leser koden, og skrivemåten b._utlaant = True er fullt lovlig for Python. Den er likevel gal i dette kurset, og den er nummerert i bokas feilregister som #11
— direkte tilgang til en instansvariabel utenfra.

Regelen er enkel og mekanisk: inne i klassen skriver du self._x fritt.
Utenfor klassen rører du aldri noe som begynner med understrek.

✏️Eksempel 1: Samme klasse, to måter å bruke den på

Vis forskjellen på å endre en instansvariabel utenfra og å gå gjennom en metode
— og hva klassen kan passe på når den får styre selv.

Klassen holder rede på to ting som hører sammen: om boka er utlånt, og hvor mange ganger den har vært det.

class Bok:
    def __init__(self, tittel):
        self._tittel = tittel self._utlaant = False self._antall_utlaan = 0

    def laan_ut(self):
        self._utlaant = True self._antall_utlaan = self._antall_utlaan + 1

    def hent_antall_utlaan(self):
        return self._antall_utlaan

b1 = Bok("Sult")
b2 = Bok("Sult")

b1.laan_ut()
b2._utlaant = True

print(b1.hent_antall_utlaan(), b2.hent_antall_utlaan())

Utskrift:

1 0

De to bøkene ser like ut utenfra — begge er «utlånt» — men tellingen stemmer
bare for den ene.
b2._utlaant = True satte det ene feltet og hoppet over det andre, fordi den som skrev linjen, ikke kunne vite at de to hørte sammen. Det er nettopp den kunnskapen klassen sitter på, og det er derfor endringen skal gå gjennom laan_ut.

Slik ville sensor sett på det: linjen b2._utlaant = True er feilkode #11 og
trekker, selv om programmet kjører. Den koster mer enn ett poeng i den store oppgaven, fordi den ofte gjentas flere steder.

Merk også at b1 og b2 har samme tittel, men er to helt forskjellige objekter.
To eksemplarer av samme bok i et bibliotek er nettopp to objekter.

📝Oppgave 1

(Innstegsoppgave — gjengivelse.) Se på klassen:

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

    def hent_navn(self):
        return self._navn

a) Hvilke av navnene under hører til objektets innside, altså de du ikke skal
røre utenfra: _navn, hent_navn, _timer?

b) Skriv linjen som henter navnet til objektet m på riktig måte.

c) Hva er galt med linjen print(m._navn)?

Løkke 2 — Aksessor og mutator (~13 min)

Når instansvariablene er stengt for omverdenen, må klassen tilby en vei inn og en vei ut. De to metodene som gjør den jobben, har faste navn i dette faget:
hent_x for å lese og sett_x for å skrive.

De er så mekaniske at du kan skrive dem uten å tenke — og det skal du, for de gir poeng i hver eneste modelleringsoppgave.

Aksessor (`hent_x`)

En metode som leser en instansvariabel og gir verdien tilbake:

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

    def hent_navn(self):
        return self._navn

m = Medlem("Ada")
print(m.hent_navn())

Utskrift:

Ada

Kroppen er alltid én linje: return self._x. Ingen parametere utover self, fordi metoden ikke trenger noe utenfra.

Ordet aksessor betyr bare «lesemetode». Møter du det i en oppgavetekst, er det denne ene linjen som etterspørres.

Mutator (`sett_x`)

En metode som endrer en instansvariabel til en verdi den får inn:

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

    def hent_navn(self):
        return self._navn

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

m = Medlem("Ada")
m.sett_navn("Nora")
print(m.hent_navn())

Utskrift:

Nora

Mutatoren har alltid en parameter i tillegg til self — den nye verdien. En mutator uten parameter er en klassisk feil: den har ingenting å sette verdien til.

Ordet mutator betyr «endringsmetode». Noen oppgavetekster sier «sett-metode»
eller «endre-metode» om det samme.

📜Aksessor/mutator-malen

Bokas tiende kodemønster, og det mest mekaniske av dem alle:

    def hent_x(self):
        return self._x

    def sett_x(self, ny):
        self._x = ny

Blokken er merket som ikke-kjørbar fordi den er en mal med plassholdernavn.

Tre regler følger med:

1. Aksessoren returnerer, den skriver ikke ut. print(self._x) i stedet for
return self._x gjør verdien ubrukelig videre.
2. Mutatoren tar imot den nye verdien som parameter. Uten parameter har den
ingenting å sette.
3. Ikke alle variabler trenger begge deler. Skriv den aksessoren og den
mutatoren oppgaven faktisk ber om — en klasse med åtte unødvendige sett-metoder er ikke bedre enn en med to nødvendige. Et medlemsnummer skal typisk kunne leses, men aldri endres.

Malen er verdt å kunne i fingrene: i den store oppgaven skriver du den fire–fem ganger, og hver gang er den verdt poeng.

✏️Eksempel 2: Full aksessor- og mutator-runde på en klasse

En kantine registrerer hver rett med navn og pris. Prisen skal kunne både leses og endres utenfra; navnet skal bare kunne leses. Skriv klassen og vis bruken.

Oppgaveteksten sier presist hvilke metoder som trengs: to for prisen, én for navnet.

class Rett:
    def __init__(self, navn, pris):
        self._navn = navn self._pris = pris

    def hent_navn(self):
        return self._navn

    def hent_pris(self):
        return self._pris

    def sett_pris(self, ny_pris):
        self._pris = ny_pris

suppe = Rett("Fiskesuppe", 89)
print(suppe.hent_navn(), suppe.hent_pris())

suppe.sett_pris(95)
print(suppe.hent_navn(), suppe.hent_pris())

Utskrift:

Fiskesuppe 89
Fiskesuppe 95

Legg merke til at det ikke finnes noen sett_navn. Oppgaven ba ikke om den,
og en rett skifter ikke navn. Å utelate den er et bevisst valg, ikke en forglemmelse — og det er lov å skrive en kommentar om at navnet med vilje er låst.

Slik ville sensor sett på det: her er det fire selvstendige poeng —
konstruktøren, hver av de to aksessorene, og mutatoren med parameter. Hadde du skrevet def sett_pris(self): uten parameter, ville de tre andre fortsatt telt.

Merk parameternavnet ny_pris. Hadde parameteren hett pris, ville koden
fortsatt virket, men det er lettere å lese når det nye og det gamle har ulike navn.

📝Oppgave 2
Eksamensnivå, sjanger…

Klassen Boks fra en resirkuleringsstasjon har self._materiale og self._vekt.

a) Skriv aksessoren for vekten.

b) Skriv mutatoren for vekten.

c) Hvorfor bør materialet bare ha aksessor, og ingen mutator?

📝Oppgave 3
Eksamensnivå, sjanger A

Hva skriver programmet ut?

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

    def hent_saldo(self):
        return self._saldo

    def sett_saldo(self, ny):
        self._saldo = ny

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

k = Konto("Ada", 500)
k.sett_inn(200)
k.sett_saldo(50)
k.sett_inn(25)
print(k.hent_saldo())

Løkke 3 — Grensesnittet (~12 min)

Tenk på en billettautomat. Utenpå har den en skjerm, noen knapper og et kortleserspor. Inni har den en skriver, en kasse og et regnskap. Det du kan bruke uten å åpne den, er grensesnittet.

For en klasse er det nøyaktig det samme: grensesnittet er de metodene andre deler av programmet kan kalle, og hvordan de kalles. Instansvariablene med understrek er innsiden.

Dette er et av de få rene teorispørsmålene på eksamen, og det kommer i to former:
«skriv grensesnittet til klassen» og «er _kaffekopper del av grensesnittet?».

Grensesnitt

Alle de offentlige metodene til en klasse, med navn og parametere — og ingenting annet.

Grensesnittet forteller hva du kan be objektet om, ikke hvordan det gjør det. Metodekroppene er ikke del av det, og instansvariablene er det heller ikke.

For klassen Rett fra Eksempel 2 er grensesnittet disse fire:

__init__(navn, pris)
hent_navn()
hent_pris()
sett_pris(ny_pris)

Blokken er merket som ikke-kjørbar fordi den er en oppstilling, ikke et program.
Legg merke til at self ikke er med: den fylles ut av Python og er ikke noe den som kaller, skal forholde seg til.

Prøvesteinen: kan en annen del av programmet skrive det, uten å bryte
konvensjonen? Da er det del av grensesnittet.

Metodesignatur

Navnet på en metode pluss parameterne den tar: sett_pris(ny_pris).

Signaturen er det den som kaller, trenger å vite — hva metoden heter, og hvor mange verdier den vil ha inn. Hva som skjer inni, er ikke signaturens sak.

I en eksamensbesvarelse skriver du gjerne grensesnittet som en liste av signaturer med en kort forklaring i naturlig språk bak hver:

hent_pris() - gir prisen i kroner sett_pris(ny_pris) - setter en ny pris

Det er nok. Oppgaven ber om signaturene, ikke om koden.

✏️Eksempel 3: Skriv grensesnittet til en klasse

Klassen Sal på et treningssenter er gitt under. Skriv grensesnittet slik en eksamensoppgave ber om det, og forklar hvorfor tre av navnene ikke er med.

Først klassen:

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

    def hent_navn(self):
        return self._navn

    def hent_ledige(self):
        return self._kapasitet - self._paameldte

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

s = Sal("Speilsal", 20)
s.meld_paa(6)
print(s.hent_navn(), s.hent_ledige())

Utskrift:

Speilsal 14

Grensesnittet er de fire metodene, med signatur og en linjes forklaring:

__init__(navn, kapasitet) - lager en sal med et navn og et antall plasser hent_navn()               - gir navnet paa salen hent_ledige()             - gir antall ledige plasser akkurat naa meld_paa(antall)          - melder paa et gitt antall deltakere

De tre som ikke er med, er _navn, _kapasitet og _paameldte. De er
instansvariabler, altså innside. At hent_ledige() regner ut svaret sitt av to av dem i stedet for å lagre det, er nettopp en detalj grensesnittet skjuler — og en detalj klassen kan endre uten at noen andre merker det.

Slik ville sensor sett på det: her gis uttelling for hver riktig signatur og
for at understrek-variablene holdes utenfor. Glemmer du __init__, mister du én linje, men resten teller.

Legg merke til at forklaringene er skrevet uten æ, ø og å. Kommentarer og oppstillinger i kode følger samme regel som koden selv.

📝Oppgave 4
Eksamensnivå, sjanger B

Klassen Sykkel har instansvariablene _kode, _stativ og _utleid, og metodene hent_kode(),
leie_ut() og lever_tilbake(nytt_stativ).

a) Er _stativ del av grensesnittet til Sykkel? Begrunn.

b) Skriv grensesnittet.

c) En medstudent sier: «_utleid må være del av grensesnittet, for ellers
kan ingen finne ut om sykkelen er utleid.» Hva er svaret?

📝Oppgave 5
Eksamensnivå, sjanger…

Se på koden:

class Rute:
    def __init__(self, nummer, kapasitet):
        self._nummer = nummer self._kapasitet = kapasitet self._passasjerer = 0

    def hent_passasjerer(self):
        return self._passasjerer

    def paa(self, antall):
        self._passasjerer = self._passasjerer + antall

r = Rute(20, 60)
r.paa(15)
r._passasjerer = 80 print(r.hent_passasjerer())

a) Hva skriver programmet ut?

b) Hvilken feilkode er den nest siste linjen, og hva er problemet med den?

c) Klassen mangler noe som ville gjort linjen unødvendig. Hva?

Løkke 4 — Mutatorer som passer på (~10 min)

Så langt har mutatorene våre vært én linje. I den store oppgaven er de ofte litt mer: de kontrollerer verdien før de setter den.

Det er den fulle gevinsten ved innkapsling. Regelen bor ett sted, og alle som bruker klassen, får den med på kjøpet — uten å vite at den finnes.

Mutator med kontroll

En mutator som sjekker verdien før den settes:

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

    def sett_alder(self, ny):
        if ny < 0:
            print("Ugyldig alder")
        else:
            self._alder = ny

    def hent_alder(self):
        return self._alder

m = Medlem("Ada")
m.sett_alder(-3)
m.sett_alder(24)
print(m.hent_alder())

Utskrift:

Ugyldig alder
24

Mønsteret er alltid det samme: if på det ugyldige tilfellet, else som setter verdien. Skriv else-grenen, ikke bare if — en mutator som setter verdien uansett og bare skriver ut en advarsel, har ikke beskyttet noe.

Hva som skal skje ved en ugyldig verdi, sier oppgaveteksten. Sier den ingenting, skriv en kommentar om hva du har valgt.

✏️Eksempel 4: Full innkapslet klasse på eksamensnivå

Et bibliotek registrerer hvert eksemplar med tittel, hylleplass og utlånsstatus.
Boka skal kunne lånes ut og leveres, og hylleplassen skal kunne endres — men tittelen ikke. Antall utlån skal kunne leses, aldri settes. Skriv klassen med et gjennomført grensesnitt, og vis den i bruk.

Oppgaveteksten er en oppskrift: den sier presist hvilke metoder som skal finnes, og dermed også hvilke som ikke skal. Klassen står i sin helhet, med bruken av den nederst i samme blokk — det andre utlånsforsøket avvises med vilje:

class Eksemplar:
    def __init__(self, tittel, hylle):
        self._tittel = tittel self._hylle = hylle self._utlaant = False self._antall_utlaan = 0

    def hent_tittel(self):
        return self._tittel

    def hent_hylle(self):
        return self._hylle

    def sett_hylle(self, ny_hylle):
        self._hylle = ny_hylle

    def er_utlaant(self):
        return self._utlaant

    def hent_antall_utlaan(self):
        return self._antall_utlaan

    def laan_ut(self):
        if self._utlaant:
            print("Allerede utlaant")
        else:
            self._utlaant = True self._antall_utlaan = self._antall_utlaan + 1

    def lever(self):
        self._utlaant = False

e = Eksemplar("Sult", "H3")
e.laan_ut()
e.laan_ut()
e.lever()
e.laan_ut()
e.sett_hylle("H7")
print(e.hent_tittel(), e.hent_hylle(), e.er_utlaant(), e.hent_antall_utlaan())

Utskrift:

Allerede utlaant
Sult H7 True 2

Fire ting er verdt å merke seg.

hent_antall_utlaan har ingen mutator, og det er med vilje: tellingen skal bare kunne gå opp gjennom laan_ut. Å sette den utenfra ville vært å forfalske statistikken.

er_utlaant heter ikke hent_utlaant. Når svaret er sant eller usant, er
er_... en mer lesbar form, og fasiten bruker den. Begge er riktige.

laan_ut er en mutator med kontroll: den avviser å låne ut noe som allerede er utlånt, og det er derfor telleren stopper på 2 og ikke 3.

Og _tittel har ingen sett-metode. Grensesnittet sier dermed at tittelen er låst — uten at noe måtte skrives om det i en kommentar.

Slik ville sensor sett på det: her gis uttelling for hver metode for seg,
pluss et eget poeng for at de metodene oppgaven ikke ba om, heller ikke er der. Hadde du glemt kontrollen i laan_ut, ville resten telt fullt.

— naturlig pausepunkt —

📝Oppgave 6
Eksamensnivå, sjanger A

Hva skriver programmet ut?

class Vare:
    def __init__(self, navn, antall):
        self._navn = navn self._antall = antall

    def hent_antall(self):
        return self._antall

    def ta_ut(self, n):
        if n > self._antall:
            print("For faa paa lager")
        else:
            self._antall = self._antall - n

v = Vare("skrue", 10)
v.ta_ut(4)
v.ta_ut(9)
v.ta_ut(6)
print(v.hent_antall())

📝Oppgave 7
Eksamensnivå, sjanger F

En sykkelutleie vil registrere hvert stativ med et navn, et antall plasser og hvor mange sykler som står der nå (som starter på null).

a) Skriv klassen Stativ med konstruktør, aksessor for navnet, og en
aksessor hent_ledige() som gir antall ledige plasser.

b) Legg til sett_inn(self, antall), som setter inn sykler bare hvis det er
plass til alle — ellers skriver den «Ikke plass» og setter ingen inn.

c) Skriv grensesnittet til klassen.

d) Hvorfor har klassen ingen sett_antall_sykler?

📝Oppgave 8
Eksamensnivå, sjanger B

Ta stilling til hver påstand om klassen under, og begrunn.

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

    def hent_navn(self):
        return self._navn

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

    def hent_poeng(self):
        return self._poeng

a) «_poeng er del av grensesnittet, siden det finnes en metode som
returnerer den.»

b) «Linjen b._poeng = 100 gir en feilmelding i Python.»

c) «Klassen kan endres til å lagre poengene som en liste av delpoeng, uten at
noen andre deler av programmet må skrives om.»

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.

Offentlig og privat

De to sidene av et objekt. Offentlig er det andre kan bruke: metodene uten understrek. Privat er innsiden: instansvariablene med understrek.

I språk som Java håndheves skillet av kompilatoren. I Python er det en avtale — og en avtale som fasiten og sensor forholder seg til som om den var håndhevet.

Skillet er også en lesehjelp. Ser du et navn med understrek i en kodesnutt, vet du med én gang at det hører til innsiden, uten å lete etter hvor det kom fra.

Aksessor

Metoden som gir deg en verdi ut av objektet: def hent_x(self): return self._x.

Kjenn den igjen på tre trekk: navnet begynner med hent_ (eller er_ når svaret er sant eller usant), den har ingen parametere utover self, og kroppen er én
return-linje.

I en oppgavetekst dukker den opp som «skriv en metode som returnerer …» eller
«klassen skal ha aksessorer for …».

Mutator

Metoden som endrer en verdi inne i objektet:
def sett_x(self, ny): self._x = ny.

Kjenn den igjen på at den alltid har en parameter i tillegg til self — den nye verdien. Uten den har den ingenting å sette.

Mutatorer kan være mer enn én linje. Skal klassen passe på en regel, står if og
else inne i mutatoren, ikke hos den som kaller.

Grensesnitt

Alle offentlige metoder til en klasse, med navn og parametere.

Instansvariablene er ikke med, heller ikke de som har en aksessor.
Metodekroppene er ikke med. self skrives ikke, fordi den som kaller, ikke oppgir den.

Testspørsmålet på eksamen har alltid samme svar: «er _kaffekopper del av grensesnittet?» — nei, men hent_kaffekopper() er det.

Implementasjon

Det motsatte av grensesnittet: hvordan klassen gjør jobben sin. Kroppene til metodene og valget av instansvariabler.

Implementasjonen kan skrives helt om uten at noen merker det, så lenge grensesnittet er det samme. En klasse kan bytte fra å lagre et tall til å lagre en liste den summerer — så lenge hent_poeng() fortsatt gir et tall.

Det er den friheten innkapsling kjøper deg, og den er grunnen til at direkte tilgang utenfra er en så dyr vane.

Konvensjon

En regel programmerere har blitt enige om, men som språket ikke håndhever.
Understreken er den viktigste i dette faget.

Andre konvensjoner du allerede følger: stor forbokstav på klassenavn, fire mellomrom innrykk, self som navn på den første parameteren.

At noe er en konvensjon, gjør det ikke valgfritt på eksamen. Fasiten følger dem, og en besvarelse som ikke gjør det, er vanskeligere å lese og gir dårligere uttelling.

`er_x()` som aksessornavn

Formen aksessoren tar når verdien er sann eller usann: er_utlaant(),
er_ledig(), er_full().

Den leses bedre enn hent_utlaant(), fordi kallet blir en påstand:
if bok.er_utlaant(): sier akkurat det den gjør. Begge former er riktige, og fasiten bruker begge.

Metoden returnerer True eller False, som Python skriver med stor forbokstav — verdt å huske til kortsvarsoppgavene.

Å velge hvilke metoder som trengs

Oppgaveteksten er oppskriften. «Prisen skal kunne oppdateres» betyr mutator;
«hver rett har et navn» betyr bare aksessor.

Skriv de metodene teksten ber om, og la resten være. En klasse med åtte unødvendige sett-metoder er ikke bedre enn en med to nødvendige — den er dårligere, fordi hver overflødig mutator åpner et hull i kontrollen.

Er teksten uklar, velg det som gir mening i domenet og skriv en kommentar om valget. Det er den ene legitime bruken av kommentarer i en besvarelse.

Kontroll i mutator

Mønsteret der mutatoren sjekker verdien før den settes:

    def sett_x(self, ny):
        if ny < 0:
            print("Ugyldig")
        else:
            self._x = ny

Blokken er en mal med plassholdernavn og er merket som ikke-kjørbar.

To ting avgjør om mønsteret er riktig skrevet: at else-grenen finnes (ellers settes verdien uansett), og at kantverdien er testet — > og >= gir ulikt svar når de to tallene er like.

Feilkode #11

Direkte tilgang til en instansvariabel utenfra: obj._x eller obj._x = verdi.

Feilen er stum. Python kjører linjen uten et pip, programmet gir riktig svar der og da, og det er nettopp derfor den er så lett å gjøre — og derfor sensor må se etter den.

Kostnaden kommer to steder: klassens egne regler hoppes over, og den delen av programmet faller sammen i det øyeblikket klassens innside endres.

Hvorfor understrek og ikke to

Python har også en form med to understreker foran, som gjør navnet vanskeligere å nå utenfra.

Denne boka bruker den ikke, og fagets løsningsforslag gjør det heller ikke. Én understrek er konvensjonen i IN1000, og det er den formen sensor forventer.

Skriv self._navn, ikke self.__navn. Og bland den ikke med __init__, som har to understreker på begge sider fordi den er en spesialmetode.

Å beskrive et grensesnitt i en kommentar

Formen en eksamensoppgave ber om når den sier «skriv grensesnittet»: en liste av metodesignaturer med en kort forklaring i naturlig språk bak hver.

hent_pris()        - gir prisen i kroner sett_pris(ny)      - setter en ny pris

Blokken er en oppstilling, ikke et program, og er derfor merket som ikke-kjørbar.

Det er nok. Oppgaven ber om signaturene, ikke om kodene — og self skal ikke med, siden den som kaller, aldri oppgir den.

Innkapsling i én setning

At objektet eier verdiene sine, og at omverdenen snakker med det gjennom metodene.

Gevinsten er tredelt: klassen kan passe på sine egne regler, den kan endres innvendig uten at noe annet må skrives om, og den som leser koden, ser med én gang hva som er ment å brukes utenfra.

Kostnaden er noen linjer ekstra. I den store oppgaven er de linjene poenggivende i seg selv, så regnestykket går alltid i din favør.

Å låse en verdi

Å utelate mutatoren med vilje, slik at en verdi bare kan settes i konstruktøren.

Tittelen på en bok, materialet i en boks, nummeret på en kollektivrute: alle er verdier som ikke endrer seg etter at objektet er laget.

Låsingen er ikke synlig i koden som en egen linje — den er fraværet av en metode. Skriv gjerne en kommentar om at det er et valg, så leseren ikke tror du glemte den.

Aksessor som regner ut

En aksessor trenger ikke å returnere en lagret verdi. Den kan regne ut svaret:

    def hent_ledige(self):
        return self._plasser - self._sykler

Blokken er et utdrag av en klasse og er merket som ikke-kjørbar.

For den som bruker klassen, er det ingen forskjell — og det er poenget.
Grensesnittet lover et tall, ikke en bestemt lagringsmåte. Å regne ut i stedet for å lagre er dessuten tryggere: verdien kan aldri komme i utakt med de to den bygger på.

Egne metoder framfor gjentatt regnestykke

Står det samme regnestykket to steder i klassen, legg det i en egen metode og kall den med self. foran.

        if antall > self.hent_ledige():

Blokken er et utdrag og er merket som ikke-kjørbar.

Gevinsten er at endringer gjøres ett sted. Skal ledige plasser senere regnes ut annerledes, treffer endringen begge stedene automatisk.

Trekk som ikke er kjørefeil

En kategori feil verdt å kjenne: koden kjører helt fint, og gir riktig svar, men bryter en konvensjon sensor ser etter.

Feilkode #11 er den viktigste. Andre i samme familie: instansvariabler uten understrek, klassenavn med liten forbokstav, aksessor som skriver ut i stedet for å returnere.

Fordi de ikke gir feilmelding, finner du dem ikke ved å teste. Du finner dem ved å lese gjennom besvarelsen med konvensjonene i hodet — bruk fem minutter på det til slutt.

Å lese hva en klasse tilbyr

Ferdigheten grensesnitt-spørsmålene måler: å se på en klasse og med én gang vite hva andre kan be den om.

Framgangsmåten er mekanisk. Stryk alt som begynner med understrek. Stryk metodekroppene. Det som står igjen — metodenavnene med parameterlistene — er grensesnittet.

Gjør du det på papir før du svarer, faller både «skriv grensesnittet» og «er
_x del av grensesnittet» av seg selv.

Understrek i prosa mot understrek i kode

En liten skrivedetalj som er verdt å vite om: et navn som _navn skrevet uten kodemarkering kan bli feil formatert i tekst.

I denne boka står derfor alle Python-navn i kodemarkering — self._navn,
hent_pris(), __init__. På eksamen i Inspera er det ikke noe problem, for der skriver du koden i et kodefelt.

Regelen er verdt å ta med seg til egne notater: skriver du om kode i vanlig tekst, marker den som kode.

To eksemplarer av samme ting

To objekter kan ha nøyaktig like verdier og likevel være helt forskjellige objekter.

Et bibliotek med to eksemplarer av «Sult» har to Eksemplar-objekter med samme tittel. Lånes det ene ut, er det andre fortsatt inne.

Dette er verdt å ha klart før kap. 5.4, der spørsmålet snus: når er to navn to objekter, og når er de to navn på det samme
objektet?

Konstruktøren som del av grensesnittet
__init__ regnes med når en oppgave ber om grensesnittet — den forteller jo hvordan et objekt lages, og hvilke verdier det trenger.

Den skrives uten self, som de andre: __init__(navn, kapasitet).

Noen oppgavetekster ber bare om «metodene klassen tilbyr». Ta konstruktøren med uansett; det trekker aldri, og glemmer du den, mister du en linje.

Innkapsling i den store oppgaven

Slik konvensjonen viser seg i Oppgave 4, som er nesten halve eksamen.

Hver klasse du skriver, får instansvariabler med understrek, aksessorer for det oppgaven vil ha ut, og mutatorer bare der noe faktisk skal kunne endres. Når to klasser samarbeider, kaller den ene metoder på den andre — aldri
annet_objekt._x.

Det siste er den vanligste formen feilkode #11 tar i den store oppgaven, og den kommer for alvor i kap. 6.2.

Hva innkapsling ikke er

Det er ikke sikkerhet. En understrek stopper ingen — verken en angriper eller en kollega med dårlig tid.

Det er ikke hemmelighold heller. Alle kan lese koden og se nøyaktig hvilke instansvariabler klassen har.

Det er arbeidsdeling: klassen tar ansvaret for sine egne regler, og resten av programmet slipper å kjenne dem. Klarer du å si det med egne ord på eksamen, har du grensesnitt-spørsmålet i boks.

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