Arv mellom klasser, super() og metodeoverstyring.
Arv og gjenbruk
Tenk deg at du har laget en god Bil-klasse med masse nyttig funksjonalitet. Nå vil du lage en Elbil-klasse. Elbiler er biler, men med noen ekstra egenskaper (batteri, lading). Må du skrive all Bil-koden på nytt?
Nei! Dette er hvor arv kommer inn. Arv lar deg lage en ny klasse basert på en eksisterende klasse – du arver alle attributter og metoder, og kan deretter legge til eller endre det du trenger.
Arv er en av de fire grunnpilarene i OOP, og handler om gjenbruk av kode og hierarkiske relasjoner.
Terminologi:
- Superklasse / Foreldrek lasse / Base class: Den klassen som arves fra
- Subklasse / Barneklasse / Derived class: Den klassen som arver
- "is-a" relasjon: En Elbil "er en" Bil – dette signaliserer at arv passer
Syntaks i Python:
class Superklasse:
pass
class Subklasse(Superklasse): # Arver fra Superklasse
passLa oss se på et konkret eksempel:
class Dyr:
def __init__(self, navn, alder):
self.navn = navn
self.alder = alder
def lag_lyd(self):
print("Dyret lager en lyd")
def beskriv(self):
print(f"{self.navn} er {self.alder} år gammel")
# Hund arver fra Dyr
class Hund(Dyr):
def lag_lyd(self):
print(f"{self.navn} sier: Voff!")
# Katt arver fra Dyr
class Katt(Dyr):
def lag_lyd(self):
print(f"{self.navn} sier: Mjau!")
# Bruk
hund = Hund("Fido", 3)
hund.beskriv() # Arvet fra Dyr: Fido er 3 år gammel
hund.lag_lyd() # Overstyrt i Hund: Fido sier: Voff!
katt = Katt("Mimir", 2)
katt.beskriv() # Arvet fra Dyr: Mimir er 2 år gammel
katt.lag_lyd() # Overstyrt i Katt: Mimir sier: Mjau!Hva skjer?
- Hund og Katt arver __init__ og beskriv fra Dyr
- De overstyrer (override) lag_lyd med sin egen versjon
- Vi trenger ikke å skrive __init__ og beskriv på nytt!
Utvide med nye attributter og metoder
Når du arver, kan du legge til nye attributter og metoder i subklassen:
class Dyr:
def __init__(self, navn, alder):
self.navn = navn
self.alder = alder
def beskriv(self):
print(f"{self.navn} er {self.alder} år gammel")
class Hund(Dyr):
def __init__(self, navn, alder, rase):
self.navn = navn
self.alder = alder
self.rase = rase # Nytt attributt
def apporter(self): # Ny metode
print(f"{self.navn} apporterer!")
# Bruk
hund = Hund("Fido", 3, "Golden Retriever")
hund.beskriv() # Fido er 3 år gammel (arvet)
hund.apporter() # Fido apporterer! (ny)
print(hund.rase) # Golden Retriever (nytt attributt)Men her er det et problem: Vi gjentar kode i __init__. Løsningen er super().
__init__) for å gjenbruke oppsettskode.Syntaks:
class Subklasse(Superklasse):
def __init__(self, ...):
super().__init__(...) # Kaller Superklasse.__init__
# ... legg til egne attributtersuper() returnerer en proxy til superklassen, slik at du kan kalle dens metoder.
La oss forbedre Hund-eksemplet med super():
class Dyr:
def __init__(self, navn, alder):
self.navn = navn
self.alder = alder
print(f"Opprettet dyr: {navn}")
def beskriv(self):
print(f"{self.navn} er {self.alder} år gammel")
class Hund(Dyr):
def __init__(self, navn, alder, rase):
super().__init__(navn, alder) # Kaller Dyr.__init__
self.rase = rase
print(f"Det er en {rase}")
def beskriv(self):
super().beskriv() # Kaller Dyr.beskriv()
print(f"Rasen er {self.rase}")
# Bruk
hund = Hund("Fido", 3, "Golden Retriever")
# Output:
# Opprettet dyr: Fido
# Det er en Golden Retriever
hund.beskriv()
# Output:
# Fido er 3 år gammel
# Rasen er Golden RetrieverFordeler med super():
- Unngår duplisering av kode
- Hvis Dyr._init_ endres, fungerer Hund fortsatt
- Klar indikasjon på hvilken metode som kalles
Override – overstyre metoder
Når en subklasse definerer en metode med samme navn som i superklassen, sier vi at den overstyrer (overrides) metoden. Dette lar oss spesialisere oppførselen:
class Bankkonto:
def __init__(self, kontonummer, saldo=0):
self.kontonummer = kontonummer
self.saldo = saldo
def ta_ut(self, belop):
if belop <= self.saldo:
self.saldo -= belop
return True
print("Ikke nok penger")
return False
class Sparekonto(Bankkonto):
def __init__(self, kontonummer, saldo=0, min_saldo=1000):
super().__init__(kontonummer, saldo)
self.min_saldo = min_saldo
def ta_ut(self, belop):
# Overstyrt versjon med ekstra regel
if self.saldo - belop < self.min_saldo:
print(f"Kan ikke ta ut – må ha minst {self.min_saldo} kr")
return False
return super().ta_ut(belop) # Kaller Bankkonto.ta_ut
# Bruk
vanlig = Bankkonto("11111111", 2000)
vanlig.ta_ut(1900) # OK
print(vanlig.saldo) # 100
spare = Sparekonto("22222222", 2000, min_saldo=1000)
spare.ta_ut(1100) # Kan ikke ta ut – må ha minst 1000 kr
spare.ta_ut(900) # OK
print(spare.saldo) # 1100Sparekonto har strengere regler, men gjenbruker logikken fra Bankkonto via super().
isinstance() – sjekke objekttype
Noen ganger må du sjekke om et objekt er av en bestemt klasse, eller arver fra en klasse. Til dette bruker vi isinstance():
class Dyr:
pass
class Hund(Dyr):
pass
class Katt(Dyr):
pass
# Lag objekter
hund = Hund()
katt = Katt()
# isinstance(objekt, Klasse) returnerer True/False
print(isinstance(hund, Hund)) # True
print(isinstance(hund, Dyr)) # True (Hund arver fra Dyr)
print(isinstance(hund, Katt)) # False
print(isinstance(katt, Katt)) # True
print(isinstance(katt, Dyr)) # True
print(isinstance(katt, Hund)) # False
# isinstance kan også sjekke mot tuple av klasser
print(isinstance(hund, (Hund, Katt))) # True (hund er Hund)Brukstilfeller:
- Validere input til funksjoner
- Ulik behandling av forskjellige objekttyper
- Unngå feil når du kaller metoder som bare finnes i visse subklasser
Hierarkier og spesialisering
Arv lar oss bygge hierarkier av klasser, fra generelle til spesifikke:
Dyr
|
+----+----+
| |
Pattedyr Fugl
| |
+---+ +---+
| | | |
Hund Katt Ørn SpurvDette kalles spesialisering (nedover i hierarkiet) eller generalisering (oppover):
class Dyr:
def __init__(self, navn):
self.navn = navn
def spis(self):
print(f"{self.navn} spiser")
class Pattedyr(Dyr):
def die(self):
print(f"{self.navn} dier")
class Hund(Pattedyr):
def bjeff(self):
print(f"{self.navn}: Voff!")
# Hund arver fra både Pattedyr og Dyr
hund = Hund("Fido")
hund.spis() # Fra Dyr
hund.die() # Fra Pattedyr
hund.bjeff() # Fra Hundis-a relasjonen gjelder hele veien:
- En Hund "er et" Pattedyr
- Et Pattedyr "er et" Dyr
- Derfor: En Hund "er et" Dyr
Dette bekreftes med isinstance:
print(isinstance(hund, Hund)) # True
print(isinstance(hund, Pattedyr)) # True
print(isinstance(hund, Dyr)) # TrueNår skal du bruke arv?
Arv er kraftig, men kan også misbrukes. Her er retningslinjer:
Bruk arv når:
- Det er en klar "is-a" relasjon (en Elbil er en Bil)
- Subklassen er en spesialisering av superklassen
- Du vil gjenbruke mye felles kode
- Endringer i superklassen skal påvirke subklassene
Ikke bruk arv når:
- Relasjonen er "has-a" (en Bil har en Motor – bruk komposisjon)
- Du bare vil gjenbruke noe kode (vurder funksjoner eller komposisjon)
- Hierarkiet blir for dypt (mer enn 3-4 nivåer)
Eksempel på "has-a" (komposisjon):
# FEIL: Bil arver fra Motor
class Motor:
pass
class Bil(Motor): # Gir ikke mening – en bil er ikke en motor!
pass
# RIKTIG: Bil har en Motor
class Motor:
def __init__(self, hestekrefter):
self.hestekrefter = hestekrefter
class Bil:
def __init__(self, merke):
self.merke = merke
self.motor = Motor(150) # Bil HAR en motorTommelfingerregel: "Favor composition over inheritance" (Foretrekk komposisjon over arv) når du er i tvil.
Oppsummering
I dette kapittelet har du lært:
- Arv: en subklasse overtar attributter og metoder fra en superklasse.
- super(): kaller superklassens metoder.
- Override: overstyre en metode i subklassen.
- isinstance(): sjekker objektets type.
- Har-en vs. er-en: arv brukes ved er-en-forhold.
Noekkelbegreper
| Begrep | Forklaring |
|---|---|
| Arv | Subklasse overtar fra superklasse |
| super() | Kaller superklassens metoder |
| Override | Overstyre en arvet metode |
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.