Att hantera fel som uppstår under körningen av ett program är en viktig del i programmeringen. Fel kommer alltid uppstå, det kan vara alltifrån att utrymmet på hårddisken är slut, datorns minne är fullt, en fil som vi ska läsa inte finns eller har fel rättigheter. Vi ska aldrig anta att användaren matar in den typ av data som vi förväntar oss.

I denna artikel ska vi gå igenom hur man hanterar olika typer av fel på ett så korrekt sätt som möjligt i Python.

Python har inbyggda funktioner för felhantering, något som faktiskt inte alla programspråk har. I Python är dessa try och except.

Ett exempelprogram

Första uppgiften är att ta reda på vilken typ av fel som kan uppstå, alltså vilken typ Traceback som genereras av olika fel. Detta gör vi genom att helt enkelt själva generera felen vi tror kommer att inträffa.

Vi gör ett mycket enkelt program som räknar ut kvadraten på ett tal. Användaren ska då mata in ett heltal och som svar ska programmet ge kvadraten på det tal som användaren skrev. Nedan visas programmet utan felhantering.

#!/usr/bin/env python3

while(True):
    tal = int(input("Ange ett heltal: "))
    svar = tal**2

    print("Kvadraten på " + str(tal) + " är " + str(svar))

Ett mycket kort och enkelt program. Vi testkör programmet och matar in några heltal för att se så att allting fungerar som det ska. Programmet avslutas genom att trycka på CTRL-D eller CTRL-C.

Ange ett heltal: 5
Kvadraten på 5 är 25
Ange ett heltal: 2
Kvadraten på 2 är 4
Ange ett heltal: 9
Kvadraten på 9 är 81
Ange ett heltal: 7
Kvadraten på 7 är 49
Ange ett heltal: Traceback (most recent call last):
  File "./felhantering1.py", line 4, in <module>
    tal = int(input("Ange ett heltal: "))
EOFError

Programmet fungerar bra, men redan här ser vi första “felet”. När jag avslutar med CTRL-D får vi en Traceback, och det ser aldrig snyggt ut. Vi ska alltid hantera alla Tracebacks på ett snyggt sätt så att användaren aldrig får sådana här felmeddelanden. För användaren kan det här se ut som programmet blew up in his face.

Vår första uppgift här blir således att hantera CTRL-D. Då börjar vi med att titta på vad just denna Traceback heter, vilket är EOFError, vilket stämmer utmärkt. EOF står för End Of File. Eftersom vi tryckte på CTRL-D fick ju inte input-funktionen någon data och då blev det ett EOF.

Två saker vill jag ska hända när användaren trycker på CTRL-D. För det första vill jag att användaren ska få en utskrift på skärmen i form av “Adjö, välkommen åter” eller liknande. Den andra saken är att programmet ska avslutas på ett korrekt sätt med noll som returvärde. Alla program i UNIX-liknande miljöer som avslutas på ett korrekt sätt ska ge noll i returvärde. Ett program som avslutas felaktigt kan ge vilket annat heltal som helst. För att åstadkomma detta importerar vi sys-modulen som har exit()-funktionen. Vi bakar sedan in raden tal = int(input("Ange ett heltal: ")) i ett try-block. Den nya koden ser nu ut som nedan.

ANNONS FÖR VÅRA EGNA BÖCKER Demonerna på internet

#!/usr/bin/env python3
import sys

while(True):
    try:
        tal = int(input("Ange ett heltal: "))
    except (EOFError):
        print("\nAdjö, välkommen åter")
        sys.exit(0)
    svar = tal**2

    print("Kvadraten på " + str(tal) + " är " + str(svar))

En testkörning av programmet visar att det hela fungerar utmärkt.

Ange ett heltal: 5
Kvadraten på 5 är 25
Ange ett heltal: 9
Kvadraten på 9 är 81
Ange ett heltal: Adjö, välkommen åter

Nu blev det en mycket snyggare avslutning på programmet. Dessutom returnerar programmet noll, vilket vi kan kontrollera med echo $? i UNIX-liknande system. Det ger siffran 0 som svar.

Men om vi istället trycker på CTRL-C för att avsluta programmet, vad händer då? Vi testar:

Ange ett heltal: 6
Kvadraten på 6 är 36
Ange ett heltal: ^CTraceback (most recent call last):
  File "./felhantering1.py", line 6, in <module>
    tal = int(input("Ange ett heltal: "))
KeyboardInterrupt

Nu fick vi istället KeyboardInterrupt. Inte alls snyggt så vi tar och lägger till denna Traceback till vår except(). Programmet ser nu ut som nedan:

#!/usr/bin/env python3
import sys

while(True):
    try:
        tal = int(input("Ange ett heltal: "))
    except (EOFError, KeyboardInterrupt):
        print("\nAdjö, välkommen åter")
        sys.exit(0)
    svar = tal**2

    print("Kvadraten på " + str(tal) + " är " + str(svar))

En testkörning visar att samma text och samma returvärde visas nu oavsett om vi avslutar med CTRL-C eller CTRL-D.

Ange ett heltal: 4
Kvadraten på 4 är 16
Ange ett heltal: ^C
Adjö, välkommen åter

Vad kan mer gå snett?

Nu när vi vet hur man lägger till exceptions är det dags att fråga oss vad som mer kan gå fel? Det vanligaste är att användaren skriver in något helt annat än ett heltal, t.ex. ett flyttal eller kanske rent av en text. Vi testar att göra så i vårt program och ser vad som händer.

Ange ett heltal: hej
Traceback (most recent call last):
  File "./felhantering1.py", line 6, in <module>
    tal = int(input("Ange ett heltal: "))
ValueError: invalid literal for int() with base 10: 'hej'
jake@red-dwarf:~/Kod$ ./felhantering1.py 
Ange ett heltal: 3.14 
Traceback (most recent call last):
  File "./felhantering1.py", line 6, in <module>
    tal = int(input("Ange ett heltal: "))
ValueError: invalid literal for int() with base 10: '3.14'

Här ser vi att fick samma sorts Traceback oavsett om vi skrev “hej” eller om vi angav 3.14 som svar, nämligen ValueError. Men vi vill inte att programmet avslutas bara för att vi skriver ett felaktigt värde. Programmet ska fortsättas precis som vanligt, men användaren ska få en hjälpande text som förklarar att han måste ange ett heltal. Vi skapar därför en ny except()-rad i koden. Den nya koden ser nu ut som nedan:

#!/usr/bin/env python3
import sys

while(True):
    try:
        tal = int(input("Ange ett heltal: "))
    except (EOFError, KeyboardInterrupt):
        print("\nAdjö, välkommen åter")
        sys.exit(0)
    except (ValueError):
        print("Ange endast heltal!")
        continue
    svar = tal**2

    print("Kvadraten på " + str(tal) + " är " + str(svar))

Det nya except()-blocket fångar nu upp alla ValueError och hanterar dem genom att först skriva ut texten “Ange endast heltal!”, därefter körs continue som gör att den nuvarande iterationen i while()-loopen hoppas över.

Vi testkör programmet och ser att allting nu faktiskt fungerar som tänkt. Programmet hanterar nu både felaktiga värden på ett snyggt sätt samt avslut av programmet med både CTRL-C och CTRL-D.

Ange ett heltal: 9
Kvadraten på 9 är 81
Ange ett heltal: hej
Ange endast heltal!
Ange ett heltal: 3.14
Ange endast heltal!
Ange ett heltal: ^C
Adjö, välkommen åter

Detta är en den generalla arbetsgången när man skapar felhantering för sin program. Man testkör för att hitta felen, lägger in hantering av de fel man fick fram, testkör igen, lägger in nya fel och testkör igen. Man håller på så tills man hittat alla möjliga fel som kan inträffa. Det händer att man trots allt missar olika typer av fel. Vad man kan göra då är att lägga in en generell except() som fångar upp alla övriga fel. Observera att man alltid bör undvika generella except()-block av den anledning att man inte på förväg vet vilka fel som inträffar. Man döljer då eventuella buggar. Det är då bättre att användaren kan rapportera den riktiga Tracebacken. Men här kan vi lägga in ett generellt except()-block efter att alla de kända felen har hanteras, mest som en demonstration.

#!/usr/bin/env python3
import sys

while(True):
    try:
        tal = int(input("Ange ett heltal: "))
    except (EOFError, KeyboardInterrupt):
        print("\nAdjö, välkommen åter")
        sys.exit(0)
    except (ValueError):
        print("Ange endast heltal!")
        continue
    except:
        print("Något har blivit riktigt galet!")
        sys.exit(1)
    svar = tal**2

    print("Kvadraten på " + str(tal) + " är " + str(svar))

Sådär, nu har vi lagt in en sista except() som fångar upp alla övriga fel som inte de två första except()-blocken hanterar. Denna sista except() avslutar dessutom programmet genom att returnera en etta som returvärde, vilket i sin tur signalerar till skalet att något oväntat har inträffat.