Tilbake
1.4
Arv og gjenbruk

1.4 Arv og gjenbruk

Arv mellom klasser, super() og metodeoverstyring.

65 min
7 oppgaver
ArvSuperMetodeoverstyringGjenbruk
Du leser den tradisjonelle versjonen
Din fremgang i kapitlet
0 / 7 oppgaver

Arv og gjenbruk

Tenk deg at du har laga ein god Bil-klasse med masse nyttig funksjonalitet. No vil du lage ein Elbil-klasse. Elbilar er bilar, men med nokre ekstra eigenskapar (batteri, lading). Må du skrive all Bil-koden på nytt?

Nei! Dette er der arv kjem inn. Arv lèt deg lage ein ny klasse basert på ein eksisterande klasse – du arvar alle attributtar og metodar, og kan deretter leggje til eller endre det du treng.

Arv er ein av dei fire grunnpilarane i OOP, og handlar om gjenbruk av kode og hierarkiske relasjonar.

Arv (Inheritance)
Arv er når ein klasse (subklassen) overtek attributtar og metodar frå ein annan klasse (superklassen).

Terminologi:
- Superklasse / Foreldreklasse / Base class: Den klassen som det vert arva frå
- Subklasse / Barneklasse / Derived class: Den klassen som arvar
- "is-a" relasjon: Ein Elbil "er ein" Bil – dette signaliserer at arv passar

Syntaks i Python:

class Superklasse:
    pass

class Subklasse(Superklasse):  # Arver fra Superklasse
    pass
✏️Enkel arv

Lat oss sjå på eit 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!

Kva skjer?
- Hund og Katt arvar __init__ og beskriv frå Dyr
- Dei overstyrer (override) lag_lyd med sin eigen versjon
- Vi treng ikkje å skrive __init__ og beskriv på nytt!

Utvide med nye attributtar og metodar

Når du arvar, kan du leggje til nye attributtar og metodar 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 eit problem: Vi gjentek kode i __init__. Løysinga er super().

super() – kalle metodane til superklassen
super() vert brukt til å kalle metodar frå superklassen. Dette er særleg nyttig i konstruktørar (__init__) for å gjenbruke oppsettskode.

Syntaks:

class Subklasse(Superklasse):
    def __init__(self, ...):
        super().__init__(...)  # Kaller Superklasse.__init__
        # ... legg til egne attributter

super() returnerer ein proxy til superklassen, slik at du kan kalle metodane hans.

✏️Bruk av super() i konstruktør

Lat oss forbetre 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 Retriever

Fordelar med super():
- Unngår duplisering av kode
- Dersom Dyr._init_ vert endra, fungerer Hund framleis
- Klar indikasjon på kva metode som vert kalla

Override – overstyre metodar

Når ein subklasse definerer ein metode med same namn som i superklassen, seier vi at han overstyrer (overrides) metoden. Dette lèt 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)  # 1100

Sparekonto har strengare reglar, men gjenbruker logikken frå Bankkonto via super().

isinstance() – sjekke objekttype

Nokre gonger må du sjekke om eit objekt er av ein bestemt klasse, eller arvar frå ein 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)

Brukstilfelle:
- Validere input til funksjonar
- Ulik behandling av forskjellige objekttypar
- Unngå feil når du kallar metodar som berre finst i visse subklassar

Hierarki og spesialisering

Arv lèt oss byggje hierarki av klassar, frå generelle til spesifikke:

        Dyr
         |
    +----+----+
    |         |
  Pattedyr  Fugl
    |         |
  +---+     +---+
  |   |     |   |
Hund Katt Ørn Spurv

Dette vert kalla 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 Hund

is-a relasjonen gjeld heile vegen:
- Ein Hund "er eit" Pattedyr
- Eit Pattedyr "er eit" Dyr
- Difor: Ein Hund "er eit" Dyr

Dette vert stadfesta med isinstance:

print(isinstance(hund, Hund))      # True
print(isinstance(hund, Pattedyr))  # True
print(isinstance(hund, Dyr))       # True

Når skal du bruke arv?

Arv er kraftig, men kan òg misbrukast. Her er retningslinjer:

Bruk arv når:
- Det er ein klar "is-a" relasjon (ein Elbil er ein Bil)
- Subklassen er ei spesialisering av superklassen
- Du vil gjenbruke mykje felles kode
- Endringar i superklassen skal påverke subklassane

Ikkje bruk arv når:
- Relasjonen er "has-a" (ein Bil har ein Motor – bruk komposisjon)
- Du berre vil gjenbruke noko kode (vurder funksjonar eller komposisjon)
- Hierarkiet vert for djupt (meir enn 3-4 nivå)

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 motor

Tommelfingerregel: "Favor composition over inheritance" (Føretrekk komposisjon over arv) når du er i tvil.

Oppsummering

I dette kapittelet har du lært:

- Arv: ein subklasse overtek attributtar og metodar frå ein superklasse.
- super(): kallar metodane til superklassen.
- Override: overstyre ein metode i subklassen.
- isinstance(): sjekkar typen til objektet.
- Har-en vs. er-en: arv vert brukt ved er-en-forhold.

Noekkelbegreper


BegrepForklaring
ArvSubklasse overtek frå superklasse
super()Kallar metodane til superklassen
OverrideOverstyre ein arva 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.