5.1 Klasser, `__init__` og `__call__`

Definere en klasse, lagre attributter i `__init__` og gjøre instansen kallbar med `__call__` — funksjonsklassen som er selve arbeidshesten.

55 min
6 oppgaver
Klasser`__init__``__call__`
Din fremgang i kapitlet
0 / 6 oppgaver
Forkunnskaper — sist du var her. Dette kapitlet bygger på kap. 1.6 om funksjoner. Tre ting derfra må sitte, og de er oppfrisket ferdig her, så du ikke trenger å bla.

1. En funksjon kan ha parametre med default-verdier. Skriver du ikke argumentet, brukes verdien i parameterlisten:

from math import exp, pi, sqrt

def gauss(x, m=0, s=1):
    return 1/(sqrt(2*pi)*s) * exp(-0.5*((x - m)/s)**2)

print(gauss(0))
print(gauss(1, 0, 2))

Utskrift:

0.3989422804014327
0.17603266338214976

2. En funksjon kan sendes som argument til en annen funksjon. Da sender du navnet UTEN parentes — tabell(kvadrat, ...), ikke tabell(kvadrat(), ...):

def tabell(f, a, b, n):
    h = (b - a)/n
    for i in range(n+1):
        x = a + i*h
        print(x, f(x))

def kvadrat(x):
    return x*x

tabell(kvadrat, 0, 1, 2)

Utskrift:

0.0 0.0
0.5 0.25
1.0 1.0

3. Innrykk avgjør hva som hører sammen. Alt som er rykket inn fire mellomrom under en def-linje, er kroppen til den funksjonen. Samme regel gjelder inne i en klasse, som du skal se nå.

Løkke 1 — Klassen som en boks med parametre (~15 min)

Problemet: parametrene som må slepes med overalt

Tenk på normalfordelingskurven, den som beskriver måleusikkerhet i et laboratorium:

f(x)=12πse(xm)2/(2s2)f(x) = \frac{1}{\sqrt{2\pi}\,s}\,e^{-(x-m)^2/(2s^2)}

Kurven har to parametre: sentrum mm og bredden ss. Skriver du den som en vanlig funksjon, må du ha tre argumenter: gauss(x, m, s).

Det er greit så lenge du bare regner ut enkeltverdier. Problemet oppstår i det du vil gi kurven videre til noe annet. En numerisk derivator, en plottefunksjon og en nullpunktsløser har alle samme forventning: de vil ha en funksjon som tar ett argument, f(x). Din funksjon tar tre.

Det er nettopp dette en klasse løser. Du pakker mm og ss inn i objektet én gang, og etterpå oppfører objektet seg som en funksjon av xx alene.

`class Navn:` — klassedefinisjonen

En klasse er en oppskrift på en type objekter. Du skriver class etterfulgt av navnet og et kolon, og alt som er rykket inn under, hører til klassen.

Navnet skrives med stor forbokstav — Gauss, Vec2D, Diff. Det er ikke et krav fra Python, men det er konvensjonen sensor forventer, og det gjør koden lesbar: stor forbokstav betyr «klasse», liten betyr «variabel eller funksjon».

class Gauss:
    pass

Klassen i seg selv er bare oppskriften. Den gjør ingenting før du lager et objekt av den.

Instans

Et konkret objekt laget etter oppskriften klassen gir. Klassen Gauss er én ting; kurven med m=0m=0 og s=1s=1 er en instans av den.

Du lager en instans ved å skrive klassenavnet med parentes: g = Gauss(0, 1). Nå er g en instans, og du kan lage så mange du vil — hver med sine egne parameterverdier.

Ordet «objekt» og ordet «instans» brukes om hverandre i faget. Bruk gjerne «instans» når du vil understreke at objektet er laget fra en bestemt klasse.

`def __init__(self, ...):` — konstruktøren

Metoden Python kaller automatisk når du lager en instans. Den kalles aldri ved navn i vanlig kode: skriver du Gauss(0, 1), kjører Python __init__ med m = 0 og s = 1 for deg.

Jobben til __init__ er å ta imot argumentene og lagre dem i instansen, ett self.navn = navn per parameter. Ingenting annet, som regel.

class Gauss:
    def __init__(self, m, s):
        self.m = m
        self.s = s

__init__ skal ikke returnere noe. Skriver du return med en verdi der, får du en feilmelding — konstruktøren lager ikke verdien, den fyller instansen.

`self`

Det første parameteret i alle metoder. self er selve instansen metoden kalles på — det er Python som sender den inn, automatisk, som første argument.

Derfor er det to ting du må vane deg til, og de er hver sin halvdel av samme regel:

- I signaturen skriver du alltid self først: def __call__(self, x):.
- I kallet skriver du den aldri: g(2), ikke g(g, 2).

Inne i en metode er self din eneste vei til instansens egne data. self.m er instansens mm; bar m er noe helt annet, nemlig en lokal variabel som ikke finnes med mindre du har laget den.

Attributt (`self.m = m`)

En variabel som hører til instansen og lever så lenge instansen lever. Linjen self.m = m oppretter attributtet m på instansen og gir det verdien argumentet hadde.

Attributter er nettopp poenget med en klasse: de er parametrene som slipper å bli sendt inn på nytt hver gang. Etter g = Gauss(0, 1) husker g for alltid at self.m er 0.

Utenfra leser du et attributt med punktum: g.m. Inne i en metode skriver du self.m. Det er samme attributt, sett fra to steder.

Hver instans har sine egne attributter. Endrer du g.m, skjer det ingenting med en annen instans h.

✏️Eksempel 1: Gauss-kurven som klasse (sjanger G, del a)

Skriv en klasse Gauss som representerer normalfordelingskurven f(x)=12πse(xm)2/(2s2)\displaystyle f(x) = \frac{1}{\sqrt{2\pi}\,s}e^{-(x-m)^2/(2s^2)}. Sentrum m og bredden s skal gis når objektet lages, og en metode verdi skal regne ut funksjonsverdien i et punkt. Lag to instanser og skriv ut noen verdier.

from math import exp, pi, sqrt

class Gauss:
    def __init__(self, m, s):
        self.m = m
        self.s = s

    def verdi(self, x):
        return 1/(sqrt(2*pi)*self.s) * exp(-0.5*((x - self.m)/self.s)**2)

g = Gauss(0, 1)
h = Gauss(2, 0.5)

print(g.m, g.s)
print(g.verdi(0))
print(h.verdi(2))
print(g.verdi(2))

Utskrift:

0 1
0.3989422804014327
0.7978845608028654
0.05399096651318806

Legg merke til tre ting i utskriften. Først: g.m og g.s er de verdiene vi sendte inn — de bor i instansen nå. Dernest: g.verdi(0) er toppunktet på standardkurven, 1/2π0.39891/\sqrt{2\pi} \approx 0.3989. Og til sist: h.verdi(2) er toppunktet på den smale kurven, som er høyere fordi arealet under kurven alltid er 1.

Den siste linjen er den viktige: g.verdi(2) gir en liten verdi, fordi x=2x = 2 ligger to standardavvik ute på g-kurven. Samme metode, samme argument, forskjellig svar — fordi de to instansene har forskjellige attributter. Det er hele poenget med en klasse.

📝Oppgave 1

(Innstegsoppgave, sjanger A — kodesporing, altså «hva skriver dette programmet ut?».) To værstasjoner er laget som instanser av samme klasse. Skriv utskriften nøyaktig, med mellomrom og linjeskift slik terminalen viser den.

class Maalestasjon:
    def __init__(self, navn, hoyde):
        self.navn = navn
        self.hoyde = hoyde

    def beskriv(self):
        return self.navn + ' ligger ' + str(self.hoyde) + ' moh.'

a = Maalestasjon('Blindern', 94)
b = Maalestasjon('Fanaraaken', 2062)
print(a.beskriv())
print(b.hoyde - a.hoyde)
print(a.navn, b.navn)

Løkke 2 — __call__: instansen som oppfører seg som en funksjon (~20 min)

`def __call__(self, x):` — kallbar instans

Spesialmetoden som gjør at du kan skrive parentes rett etter objektet. Har klassen en __call__, er g(2) lovlig, og det betyr nøyaktig det samme som g.__call__(2).

Da er g kallbar: den kan brukes overalt der Python venter en funksjon av ett argument. Det er dette som løser problemet vi startet med — parametrene bor i instansen, og utsiden ser bare f(x).

class Gauss:
    def __init__(self, m, s):
        self.m = m
        self.s = s

    def __call__(self, x):
        return exp(-0.5*((x - self.m)/self.s)**2)

Uten __call__ er g(2) en feil: «'Gauss' object is not callable». Med den kan du skrive g(2), sende g til en derivator, eller plotte den — helt som en vanlig funksjon.

📜Funksjonsklasse-malen

Dette er kodemønsteret Del 5 handler om, og det du skal kunne skrive fra bunnen uten å tenke. Det består av tre linjer struktur og én linje matematikk:

class Navn:
    def __init__(self, p1, p2):
        self.p1 = p1
        self.p2 = p2

    def __call__(self, x):
        return ...  # formelen, med self.p1 og self.p2

Arbeidsdelingen er fast, og den er hele hemmeligheten:

1. __init__ tar imot alt som er konstant for kurven — parametre, konstanter, en annen funksjon, et steg. Ett self. per argument.
2. __call__ tar imot det som varierer — nesten alltid den ene variabelen xx (eller tt) — og returnerer funksjonsverdien.

Går du feil her, går resten av oppgaven feil, så still deg selv spørsmålet før du skriver: hva er fast for kurven, og hva varierer? Det faste hører i __init__, det varierende i __call__.

✏️Eksempel 2: Når kjøres hvilken metode?

Det som gjør spesialmetoder forvirrende er ikke syntaksen, men at ingen av dem kalles ved navn. La oss legge inn en utskrift i hver metode, slik at rekkefølgen av kall blir synlig i terminalen.

from math import exp

class Gauss:
    def __init__(self, m, s):
        print('__init__ kjorer, m =', m, 's =', s)
        self.m = m
        self.s = s

    def __call__(self, x):
        print('__call__ kjorer, x =', x)
        return exp(-0.5*((x - self.m)/self.s)**2)

print('for instansiering')
g = Gauss(0, 2)
print('etter instansiering')
y = g(2)
print('verdien er', y)

Utskrift:

for instansiering
__init__ kjorer, m = 0 s = 2
etter instansiering
__call__ kjorer, x = 2
verdien er 0.6065306597126334

Les utskriften ovenfra og ned, for den forteller hele historien:

- __init__ kjører én gang, i det øyeblikket Gauss(0, 2) utføres. Argumentene 0 og 2 går til __init__.
- Mellom de to metodene skjer det ingenting. Instansen bare ligger der med attributtene sine.
- __call__ kjører først når du skriver g(2). Argumentet 2 går til __call__, ikke til __init__ — det er to helt forskjellige parameterlister.

De to print-linjene inne i metodene står her bare for å avsløre rekkefølgen. Fjern dem i en eksamensbesvarelse: sensor vil ha kort, ren kode uten ekstra utskrift.

📝Oppgave 2

(Midtveisnivå, sjanger A — kodesporing.) Programmet under er kjørt ferdig, slik at r finnes.

class Rampe:
    def __init__(self, a):
        self.a = a

    def __call__(self, t):
        return self.a*t

r = Rampe(3)

For hvert av de fire uttrykkene under: si hvilken metode Python kjører (eller at ingen metode kjøres), og hva uttrykket har som verdi.

a) Rampe(5)

b) r(4)

c) r.a

d) r.__call__(4)

✏️Eksempel 3: Instansen brukt der en funksjon er ventet

Skriv en funksjon maksverdi(f, a, b, n) som finner den største verdien av f i n+1n+1 jevnt fordelte punkter på [a,b][a, b]. Bruk den på to Gauss-kurver — uten å endre en linje i maksverdi.

from math import exp, pi, sqrt

class Gauss:
    def __init__(self, m=0, s=1):
        self.m = m
        self.s = s

    def __call__(self, x):
        return 1/(sqrt(2*pi)*self.s) * exp(-0.5*((x - self.m)/self.s)**2)

def maksverdi(f, a, b, n):
    stor = f(a)
    for i in range(1, n+1):
        x = a + i*(b - a)/n
        if f(x) > stor:
            stor = f(x)
    return stor

standard = Gauss()
smal = Gauss(2, 0.5)
print(maksverdi(standard, -4, 4, 80))
print(maksverdi(smal, -4, 4, 80))

Utskrift:

0.3989422804014327
0.7978845608028654

Se på signaturen til maksverdi: den vet ingenting om klasser. Den kaller bare f(x). Likevel virker den, fordi begge instansene er kallbare.

Dette er grunnen til at funksjonsklassen er så nyttig i faget. Alt maskineriet du møter senere — numerisk derivasjon, integrasjon, ODE-løsere, plotting — ber om «en funksjon av ett argument». En instans med __call__ er en slik funksjon, men med parametre på innsiden.

Legg også merke til Gauss() uten argumenter. Det virker fordi parameterlisten har default-verdier, m=0 og s=1.

Utskriftsdetalj (feil #16 — utskriftsdetaljer oversett): den øverste verdien er toppen av standardkurven, 1/2π1/\sqrt{2\pi}. Den skrives med alle desimalene Python har, fordi vi ikke ba om formatering.

Default-verdi i `__init__` (`h=1e-5`)

En parameter kan ha en standardverdi som brukes hvis den som lager instansen ikke oppgir noe. Formen er def __init__(self, f, h=1e-5):.

Da virker begge disse:

- Diff(sin)h blir 1e-5.
- Diff(sin, 1e-3)h blir 1e-3.

Regelen er at parametre med default-verdi må stå sist i parameterlisten. def __init__(self, h=1e-5, f): er en syntaksfeil.

Eksamensoppgaver ber ofte eksplisitt om en default-verdi («med et steg h som har standardverdi 10510^{-5}»). Da er det ett poeng å skrive den inn i signaturen, ikke inne i metodekroppen.

Løkke 3 — Klassen som pakker inn en annen funksjon (~20 min)

Attributtet trenger ikke være et tall

Så langt har attributtene våre vært tall. Men self.f = f er like lovlig: et attributt kan være en funksjon. Da har du en klasse som pakker inn en annen funksjon og gjør noe med den.

Det er nettopp det mønsteret eksamen bruker til numerisk derivasjon. Du skal lage et objekt som er «den deriverte av f» — og som selv er en vanlig funksjon du kan kalle, tabulere eller plotte.

Den sentrerte deriverte er

f(x)f(x+h)f(xh)2hf'(x) \approx \frac{f(x+h) - f(x-h)}{2h}

med et lite steg hh. Nå skal formelen inn i en __call__.

✏️Eksempel 4: `Diff` — den deriverte som klasse (eksamensnivå)

Skriv en klasse Diff som tar en funksjon f og et steg h med standardverdi 10510^{-5} i konstruktøren, og der kallet d(x) returnerer den sentrerte deriverte (f(x+h)f(xh))/(2h)(f(x+h)-f(x-h))/(2h). Vis at Diff(sin) oppfører seg som cosinus.

from math import sin, cos

class Diff:
    def __init__(self, f, h=1e-5):
        self.f = f
        self.h = h

    def __call__(self, x):
        f, h = self.f, self.h
        return (f(x + h) - f(x - h))/(2*h)

dsin = Diff(sin)
print(dsin(0))
print(cos(0))
print(dsin(1))
print(cos(1))

grov = Diff(sin, 0.1)
print(grov(1))

Utskrift:

0.9999999999833332
1.0
0.5403023058569989
0.5403023058681398
0.53940225216976

Kodemønsteret er malen fra i stad, med én ny vending: self.f er en funksjon, så f(x + h) inne i __call__ kaller den funksjonen brukeren sendte inn.

Linjen f, h = self.f, self.h er en vanlig og god vane i dette faget. Den koster én linje og gjør formelen etterpå lesbar — sammenlign med (self.f(x + self.h) - self.f(x - self.h))/(2*self.h), som er samme sak, men vondt å lese og lett å skrive feil.

Se på tallene: med h=105h = 10^{-5} treffer den numeriske deriverte cosinus til rundt ti desimaler. Med h=0,1h = 0{,}1 (den siste instansen) sitter bare de to første — avviket er rundt 91049\cdot 10^{-4}. Det er hele avveiningen i numerisk derivasjon i et nøtteskall.

Uttelling på eksamen: de tre poengbærende elementene her er (i) at __init__ lagrer både f og h som attributter, (ii) at default-verdien står i signaturen, og (iii) at __call__ bruker den sentrerte formelen med 2*h i nevneren. Bytter du fortegn på et ledd, mister du et halvt poeng — ikke oppgaven.

📝Oppgave 3

(Midtveisnivå, sjanger A — kodesporing.) Hva skriver programmet ut? Skriv utskriften nøyaktig, med mellomrom og linjeskift slik terminalen viser den.

class Potens:
    def __init__(self, n=2):
        self.n = n

    def __call__(self, x):
        return x**self.n

p = Potens()
q = Potens(3)
print(p(4), q(2))
print(p(2) + q(1))
print(p.n, q.n)

📝Oppgave 4
Eksamensnivå, sjanger G
En avkjølingsmodell for en kopp kaffe sier at temperaturen etter tiden tt er

T(t)=Tomg+(T0Tomg)ektT(t) = T_{\text{omg}} + (T_0 - T_{\text{omg}})\,e^{-kt}

der TomgT_{\text{omg}} er romtemperaturen, T0T_0 starttemperaturen og kk en avkjølingskonstant.

a) Skriv en klasse Avkjoling der de tre parametrene gis når objektet lages, og der kallet T(t) gir temperaturen ved tiden t.

b) Lag en instans med Tomg=20T_{\text{omg}} = 20, T0=90T_0 = 90 og k=0,05k = 0{,}05, og skriv ut temperaturen ved t=0t = 0, t=10t = 10 og t=60t = 60.

📝Oppgave 5
Eksamensnivå, sjanger O

Programmet under skal regne ut en Gauss-verdi, men det virker ikke. Finn alle feilene, forklar hva hver av dem gjør, og skriv programmet riktig.

class Gauss:
    def __init__(m, s):
        m = m
        s = s

    def __call__(x):
        return exp(-0.5*((x - self.m)/self.s)**2)

g = Gauss(0, 1)
print(g(1))

📝Oppgave 6
Eksamensnivå, sjanger G
En bakteriekultur vokser etter den logistiske modellen

N(t)=K1+(KN0N0)ertN(t) = \frac{K}{1 + \left(\frac{K - N_0}{N_0}\right)e^{-rt}}

der KK er bærekapasiteten, N0N_0 startbestanden og rr vekstraten.

a) Skriv en klasse Logistisk der K, N0 og r gis i konstruktøren, og der kallet gir bestanden ved tiden t. Vekstraten skal ha standardverdien r=0,5r = 0{,}5.

b) Skriv ut bestanden ved t=0,5,10,20t = 0, 5, 10, 20 for K=1000K = 1000 og N0=10N_0 = 10, med to desimaler.

c) Bruk klassen Diff fra eksempel 4 til å regne ut vekstfarten N(10)N'(10) — uten å endre en linje i Diff.

API- og konstruksjonsliste

Begrepsbank

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

Metode

En funksjon som er definert inne i en klassekropp. Metoder kalles på en instans med punktum: g.verdi(2).

Forskjellen fra en vanlig funksjon er self: metoden har tilgang til instansens attributter, og trenger derfor færre argumenter.

verdi, __init__ og __call__ er alle metoder. De to siste er i tillegg spesialmetoder, og det er den eneste forskjellen på dem.

Spesialmetode (dunder-metode)

En metode med to understreker på hver side av navnet, som Python kaller automatisk i bestemte situasjoner. __init__ kalles av Navn(...), __call__ av obj(...).

I dagligtale kalles de «dunder-metoder», etter «double underscore». Du gjenkjenner dem på formen __navn__.

Det som gjør dem forvirrende for nybegynneren, er nettopp at de ikke kalles ved navn: du skriver g(2) og __call__ kjører. Regel når du sporer kode: finn ut hva uttrykket ser ut som, og spør hvilken spesialmetode den formen utløser.

IN1900 bruker fem av dem: __init__, __call__, __str__, __add__ og __mul__. De tre siste kommer i kap. 5.2.

Instansiering (`obj = Navn(...)`)

Handlingen å lage en instans fra en klasse. Du skriver klassenavnet med parentes, og argumentene i parentesen går til __init__.

To ting skjer, i denne rekkefølgen: Python lager et tomt objekt, og deretter kjører __init__ på det og fyller det med attributter.

Verdien av uttrykket Navn(...) er instansen selv. Derfor kan du både lagre den i en variabel, sende den rett inn i en funksjon, eller putte den i en liste.

Funksjonsklasse

En klasse som representerer én matematisk funksjon med parametre: konstruktøren tar parametrene, og __call__ regner ut funksjonsverdien.

Dette er bokas viktigste klassetype, og eksamensoppgavene bruker den om alt fra hyperbolske funksjoner til polynomer og deriverte.

Kjennetegnet er at instansen kan brukes overalt der Python venter f(x). Du kan tabulere den, derivere den numerisk, plotte den eller sende den til en ODE-løser.

Kallbart objekt

Et objekt du kan skrive parentes etter. Vanlige funksjoner er kallbare, og en instans er kallbar hvis — og bare hvis — klassen har en __call__.

Mangler den, sier Python fra med siste linje: TypeError: 'Rampe' object is not callable. Det er en av de mest presise feilmeldingene i Python, og den betyr nesten alltid at du glemte __call__ eller stavet den feil.

Vær nøye med navnet: __call__ med to understreker på hver side. _call_ eller __call gjør ingenting — det blir bare en vanlig metode med et rart navn.

Attributt-tilgang: `obj.a` mot `self.a`

Det samme attributtet, sett fra to steder. Utenfor klassen bruker du instansnavnet: g.m. Inne i en metode bruker du self: self.m.

Du kan også endre et attributt utenfra, med g.m = 5. Det er lovlig Python, og det gjør at neste kall til g(x) regner med den nye verdien. På eksamen dukker dette opp i sporingsoppgaver: en instans lages, et attributt endres, og så kalles instansen.

Attributt-tilgang kjører ingen metode. Det er bare et oppslag i objektet.

Lokal variabel i en metode

En variabel som opprettes uten self. inne i en metode. Den finnes bare mens metoden kjører, og forsvinner når metoden er ferdig.

Det er dette som gjør m = m i en konstruktør til en stille katastrofe: linjen er lovlig, programmet krasjer ikke der, men instansen får aldri noe attributt. Feilen viser seg først senere, som AttributeError eller NameError et helt annet sted.

Lokale variabler er nyttige når du vil kort ned en formel, som i f, h = self.f, self.h. Skillet du må ha automatisk: med self. = lagres i instansen, uten self. = forsvinner.

`__init__` returnerer ingenting

Konstruktørens jobb er å fylle instansen, ikke å levere en verdi. Den har ingen return-setning med verdi.

Prøver du likevel return self.m + self.s i __init__, får du TypeError: __init__() should return None. Verdien av Navn(...) er nemlig alltid instansen, og den bestemmer du ikke over.

Konsekvensen i praksis: g = Gauss(0, 1) gir deg objektet, aldri et tall. Vil du ha et tall ut, må du kalle instansen — g(2) — eller lese et attributt.

De to parentesparene: `Navn(...)` mot `obj(...)`

Samme tegn, to helt forskjellige jobber, og det avhenger av hva som står rett foran parentesen.

- Parentes etter et klassenavnGauss(0, 1) — lager en instans og kjører __init__.
- Parentes etter en instansg(2) — kjører __call__.

Dette er den enkle regelen som løser de fleste sporingsoppgavene om klasser. Se på navnet foran parentesen: stor forbokstav betyr som regel «her lages det et objekt», liten forbokstav betyr «her brukes et objekt».

Instanser er uavhengige av hverandre

Hver instans har sitt eget sett attributter. g = Gauss(0, 1) og h = Gauss(2, 0.5) deler ingenting: g.s er 1 uansett hva som skjer med h.s.

Det er derfor det er billig å lage mange instanser. Én kurve per parametersett, og alle kan brukes side om side i samme program — for eksempel plottes i samme figur.

Sporingsfellen som følger av dette: når to instanser er laget fra samme klasse, må du holde to kolonner i sporingstavlen din, én per instans. Blander du dem, får du feil svar selv om du forstår koden.

Attributt mot argument (`self.a` mot `a`)

Inne i __init__ finnes navnet i to versjoner: a er argumentet som kom inn, self.a er attributtet du lagrer det i. Linjen self.a = a kopierer det ene til det andre.

Etter at __init__ er ferdig, finnes bare self.a. Derfor er return a*t i en annen metode en NameError: argumentet er borte, bare attributtet lever.

At de to har samme navn, er en ren vane — self.a = verdi ville fungert like godt. Men vanen er sensors vane også, så behold den.

Arbeidsdelingen: fast i `__init__`, variabelt i `__call__`

Den ene avgjørelsen som bestemmer om en G-oppgave blir riktig: hvilke størrelser hører hvor.

- Det som er fast for objektet — parametre, konstanter, en innpakket funksjon, et steg — settes i __init__.
- Det som varierer fra kall til kall — nesten alltid den ene variabelen xx eller tt — er parameter i __call__.

Testen er praktisk: hvis du må lage en ny instans for hver xx-verdi, har du plassert noe galt.

Repetisjon — fem spørsmål før du går videre

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.