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 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.

Arv (Inheritance)
Arv er når en klasse (subklassen) overtar attributter og metoder fra en annen klasse (superklassen).

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
    pass
✏️Enkel arv

La 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().

super() – kalle superklassens metoder
super() brukes til å kalle metoder fra superklassen. Dette er spesielt nyttig i konstruktører (__init__) for å gjenbruke oppsettskode.

Syntaks:

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

super() returnerer en proxy til superklassen, slik at du kan kalle dens metoder.

✏️Bruk av super() i konstruktør

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 Retriever

Fordeler 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)  # 1100

Sparekonto 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 Spurv

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

is-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))       # True

Nå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 motor

Tommelfingerregel: "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


BegrepForklaring
ArvSubklasse overtar fra superklasse
super()Kaller superklassens metoder
OverrideOverstyre 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.