Unntak#

Unntak (exceptions) har du sikkert sett før.

print(1/0) 
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
Cell In[1], line 1
----> 1 print(1/0) 

ZeroDivisionError: division by zero

I koden over forsøker vi å dele på \(0\). Det skaper en feilmelding med unntaket ZeroDivisionError.

Det finnes en del andre unntak også, men her er en liste over noen av de mest typiske.

IndexError
liste = [1, 2, 3]
print(liste[3])
Forklaring

Når en indeks er utenfor størrelsen av et indekserbart objekt.

KeyError
ordbok = {"A":"B","B":"C"}
print(ordbok["D"])
Forklaring

Når man forsøker å finne verdien til en nøkkel som ikke er i ordboken.

TypeError
print("🐍" / 3)
Forklaring

Når man bruker feil type. I eksempelet forsøker man å dele en string på et heltall.

ValueError
import math
print(sqrt(-2))
Forklaring

Når man bruker en verdi som er utenfor et gitt definisjonsområde. For eksempel klarer ikke math å bruke sqrt på et negativt tall

numpy og cmath gir deg ikke feil, men et komplekst tall.

ZeroDivisionError
print(1/0)
Forklaring

«Å dele på null er tull» ☝️🤓

FileNotFoundError
with open("test.txt") as file:
    linjer = file.readlines()
print(linjer)
Forklaring

Når man prøver å finne en fil som ikke finnes.

Alle disse unntakene arver fra et mer generelt unntak Exception.

Skape feilmeldinger med raise#

Noen ganger ønsker vi å skape feilmeldinger. Dette kan vi gjøre med nøkkelordet raise.

def fart(strekning, tid):
    if tid != 0:
        return f"Farten er {strekning/tid:.2f} m/s"
    else:
        raise ZeroDivisionError("Tidsargumentet kan ikke være lik 0.")

print(fart(60, 11))
print(fart(60, 0))
Farten er 5.45 m/s
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
Cell In[1], line 8
      5         raise ZeroDivisionError("Tidsargumentet kan ikke være lik 0.")
      7 print(fart(60, 11))
----> 8 print(fart(60, 0))

Cell In[1], line 5, in fart(strekning, tid)
      3     return f"Farten er {strekning/tid:.2f} m/s"
      4 else:
----> 5     raise ZeroDivisionError("Tidsargumentet kan ikke være lik 0.")

ZeroDivisionError: Tidsargumentet kan ikke være lik 0.

Som vi ser regner programmet farten dersom s=60 og t=11, men om vi setter t=0 så sendes det et unntak ZeroDivisionError med den feilmeldingen som vi laget selv.

Her er et eksempel med klasser og TypeError:

class Elev:
    def __init__(self, navn):
        self.navn = navn

class Lærer:
    def __init__(self, navn, fag):
        self.navn = navn

class Klasse:
    def __init__(self, navn):
        self.elever = []

    def legg_til_elev(self, x):
        if isinstance(x, Elev):
            self.elever.append(x)
        else:
            raise TypeError("Du kan bare legge til elever med denne metoden.")

klasse = Klasse("10B")
elev = Elev("Petter")
lærer = Lærer("Einar", "Gym")
klasse.legg_til_elev(elev)
klasse.legg_til_elev(lærer)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[17], line 23
     21 lærer = Lærer("Einar", "Gym")
     22 klasse.legg_til_elev(elev)
---> 23 klasse.legg_til_elev(lærer)

Cell In[17], line 17, in Klasse.legg_til_elev(self, x)
     15     self.elever.append(x)
     16 else:
---> 17     raise TypeError("Du kan bare legge til elever med denne metoden.")

TypeError: Du kan bare legge til elever med denne metoden.

Vi får en hensiktsmessig feilmelding om vi bruker metoden på feil måte.

Hvordan hindre at objekter lages av en spesiell klasse

Noen ganger ønsker vi at man ikke skal kunne lage objekter av en klasse.

For å hindre dette kan vi modifisere konstruktøren til å sende en feilmelding om man lager et objekt av samme type().

class Kjøretøy:
    def __init__(self, merke):
        if type(self) == Kjøretøy:
            raise Exception("Du kan ikke lage objekter av klassen Kjøretøy. Bruk subklassene i stedet.")
        self.merke = merke

Brukeren kan fortsatt lage objekter av subklassene, men om brukeren prøver å lage et objekt med

bil = Kjøretøy("Volvo")

Så får brukeren feilmeldingen:

Exception: Du kan ikke lage objekter av klassen Kjøretøy. Bruk subklassene i stedet.

Unngå feilmeldinger med try og except#

Vi kan dodge feilmeldinger ved å bruke try og except 🧍💨🤸💨🧍.

Jeg vil lage et program hvor man har en ordbok som holder styr på hvem som har poeng i et spill. Etter en runde ønsker jeg å øke poengene til alle som er i listen vinnere, men hvis jeg prøver å øke poengene til noen som ikke er i ordboken, får jeg en KeyError.

poeng = {"Yosef" : 1, "Hannah" : 1}

vinnere = ["Yosef", "Matheus", "Hannah"]

for x in vinnere:
    poeng[x] += 1
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
Cell In[6], line 6
      3 vinnere = ["Yosef", "Matheus", "Hannah"]
      5 for x in vinnere:
----> 6     poeng[x] += 1

KeyError: 'Matheus'

Hvis jeg får en KeyError betyr det at personen ikke er i ordboken. Da kan vi bruke try og except for å fange unntaket KeyError, og legge inn personen hvis det er tilfellet.

poeng = {"Yosef" : 1, "Hannah" : 1}

vinnere = ["Yosef", "Matheus", "Hannah"]

for x in vinnere:
    try:
        poeng[x] += 1
    except KeyError:
        poeng[x] = 1

print(poeng)
{'Yosef': 2, 'Hannah': 2, 'Matheus': 1}

Oppgaver#

Oppgave 1 🐎

Her er begynnelsen på et objektorientert program om en stall og noen dyr.

class Stall:
    def __init__(self):
        self.stallplasser = []
    
    def sett_inn(self, x):
        self.stallplasser.append(x)

class Hund:
    def __init__(self, navn):
        self.navn = navn

class Hest:
    def __init__(self, navn):
        self.navn = navn

Modifiser klassen Stall slik at man får en feilmelding med TypeError dersom man prøver å kjøre sett_inn med et objekt som ikke er en Hest.

Feilmeldingen skal også si hvilken type objekt du har forsøkt å legge inn som ikke passer.