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».
Understrek foran instansvariablene og metoder som heter hent_... og sett_... er standardformen i hver eneste klassemodellering i settene vi har talt — altså i Oppgave 4, som alene er nesten halve eksamen. Det er ikke pynt: sensor ser etter det, og fasiten skriver det slik hver gang.
Dette må sitte — bokas høyeste prioritetsnivå. Selve konvensjonen er
mekanisk, og du kan sikre deg poengene ved å skrive den likt hver gang.
Ett begrep i tillegg ligger på nivået kjenne til: grensesnittet. Det er testet to ganger i de settene vi har gjennomgått — én gang som «skriv grensesnittet til klassen» i den store oppgaven, og én gang som et flervalg om hvorvidt en understrek-variabel er del av grensesnittet. Svaret på det siste er nei, og det er verdt to poeng du kan ta med deg gratis.
Sjangrene som treffes her, er sjanger B — flervalg om objekter, altså
«hvilken påstand om denne koden er sann?», og sjanger F — stor klassemodellering, altså å bygge samarbeidende klasser fra en beskrivelse.
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 Hamsun2. 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 = TrueIngen 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.
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.
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.
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 0De 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.
(Innstegsoppgave — gjengivelse.) Se på klassen:
class Medlem:
def __init__(self, navn):
self._navn = navn self._timer = 0
def hent_navn(self):
return self._navna) 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.
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:
AdaKroppen 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.
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:
NoraMutatoren 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.
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 = nyBlokken 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.
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 95Legg 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.
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?
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?».
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.
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 prisDet er nok. Oppgaven ber om signaturene, ikke om koden.
Understreken bærer to budskap på én gang, og de blandes lett:
- «Dette er en instansvariabel» — verdien hører til objektet og settes i
konstruktøren.
- «Dette er ikke del av grensesnittet» — ingen utenfor klassen skal lese
eller skrive den.
De to henger sammen, men de besvarer ulike spørsmål. Spør et flervalg «hvilke av disse er instansvariabler?», skal _kapasitet med. Spør det «hva er del av grensesnittet til klassen?», skal _kapasitet ikke med — men hent_kapasitet() skal.
Leser du spørsmålet raskt, er det lett å svare på det andre når det ble spurt om det første. Les hvilket av de to ordene som står der: instansvariabel eller
grensesnitt.
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 14Grensesnittet 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 deltakereDe 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.
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?
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.
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
24Mø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.
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 2Fire 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, erer_... 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 —
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())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?
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._poenga) «_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.»
Fem feil koster nesten alle poengene som mistes på innkapsling:
- #11 — direkte tilgang utenfra. obj._x eller obj._x = verdi utenfor
klassen. Python sier ikke fra, men det trekker, og det gjentas ofte flere steder i samme besvarelse.
- Mutator uten parameter. def sett_pris(self): har ingenting å sette
verdien til. Mutatoren har alltid én parameter i tillegg til self.
- Aksessor som skriver ut i stedet for å returnere. print(self._x) gir
None tilbake, og alt som bygger på metoden, faller.
- Tro at understrek-variabler er del av grensesnittet. Grensesnittet er
metodene, med navn og parametere. Variablene er innside — også de som har en aksessor.
- Mutator uten else. En sjekk som skriver «Ugyldig» og så setter verdien
likevel, har ikke beskyttet noe.
Og én vane som ikke er en feil, men som koster tid: å skrive hent- ogsett-metoder for alt. Skriv dem oppgaven ber om, og la resten være.
Begrepsbank
Begrepsbanken er flashcard- og repetisjonsstoff — den gjentar det du nettopp har lest. Hopp trygt over ved førstegangslesing; tidsanslaget for kapitlet gjelder kjernestoffet.
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.
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 énreturn-linje.
I en oppgavetekst dukker den opp som «skriv en metode som returnerer …» eller
«klassen skal ha aksessorer for …».
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 ogelse inne i mutatoren, ikke hos den som kaller.
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.
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.
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.
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.
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.
Mønsteret der mutatoren sjekker verdien før den settes:
def sett_x(self, ny):
if ny < 0:
print("Ugyldig")
else:
self._x = nyBlokken 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.
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.
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.
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 prisBlokken 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.
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.
Å 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.
En aksessor trenger ikke å returnere en lagret verdi. Den kan regne ut svaret:
def hent_ledige(self):
return self._plasser - self._syklerBlokken 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å.
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.
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.
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.
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 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?
__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.
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 — aldriannet_objekt._x.
Det siste er den vanligste formen feilkode #11 tar i den store oppgaven, og den kommer for alvor i kap. 6.2.
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.
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.