Poikkeukset

Ei ole harvinaista, että ohjelmaa suoritettaessa törmätään virhetilanteisiin. Osa virheistä johtuu ohjelmointivirheistä, mutta osa on sellaisia, joihin ohjelmoija ei voi mitenkään vaikuttaa. Esimerkiksi ohjelma pyytää käyttäjältä kokonaisluvun, mutta käyttäjä antaakin kirjaimia. Tai ohjelman pitäisi tallentaa tietoja tiedostoon, mutta kovalevytila on jo täynnä.

Suurta osaa näistä virhetilanteista pystyisi periaateessa käsittelemään if-else-rakenteiden avulla. Niitä käytettäessä ohjelman rakenteesta tulee kuitenkin helposti monimutkainen. Lisäksi ohjelman päätehtävään liittyvät käskyt saattavat hukkua erilaisia virhetilanteita käsittelevien if-else-rakenteiden sisään.

Python tarjoaa virhetilanteiden käsittelyyn oman mekanismin, poikkeukset. Jos ohjelman suorituksessa sattuu virhetilanne eli syntyy ns. poikkeus, johon on ennalta varauduttu, se voidaan käsitellä tryexcept-rakenteen avulla.

Rakenteen yleinen muoto on seuraava:

try:
    # Jono käskyjä, joista jokin tai jotkin voivat aiheuttaa tyypin
    # poikkeuksen_tyyppi poikkeuksen.
except poikkeuksen_tyyppi:
    # Käskyjä, jotka jotenkin selvittävät virhetilanteen, jos on
    # aiheutunut poikkeuksen_tyyppi-tyyppinen poikkeus.

Tällainen koodi suoritetaan siten, että try-osassa olevia käskyjä suoritetaan yksi kerrallaan normaaliin tapaan. Jos jonkin käskyn suoritus aiheuttaa poikkeuksen, jonka tyyppi on poikkeuksen_tyyppi, hypätään välittömästi suorittamaan except-osassa olevia käskyjä. Kun except-osan käskyt on suoritettu, ei enää palata try-osaan, vaan jatketaan ohjelman suoritusta except-osan jälkeen tulevasta ohjelman osasta.

Jos try-osan suorituksessa ei aiheudu poikkeuksia, except-osan käskyjä ei suoriteta lainkaan, vaan try-osan suorituksen jälkeen siirrytään ohjelmassa except-osan jälkeen tuleviin käskyihin.

Rakenteessa voi olla useita except-osia erityyppisiä poikkeuksia varten. Suoritettava except-osa valitaan aina aiheutuneen poikkeuksen tyypin mukaan.

Except-osaan kirjoitettava koodi vaihtelee tilanteen mukaan. Joskus (jos esimerkiksi ohjelman pitäisi kirjoittaa tiedostoon, mutta kovalevytila on täynnä), ei ole muuta vaihtoehtoa kuin antaa käyttäjälle selväsanainen virheilmoitus siitä, mitä on tapahtunut. Tämäkin on kuitenkin parempi vaihtoehto kuin se, että ohjelma vain kaatuu, eikä käyttäjä saa selville, mikä on mennyt pieleen. Joskus taas käskyt voidaan suunnitella siten, että ne todella korjaavat virhetilanteen, esimerkiksi pyytävät käyttäjältä uutta syötettä käyttäjän antaman kelvottoman syötteen tilalle.

Ensimmäisessä esimerkissä käyttäjälle annetaan vain virheilmoitus väärästä syötteestä. Ohjelman on tarkoitus muuttaa käyttäjän nauloina antama massa kilogrammoiksi. Massa nauloina pitää antaa kokonaislukuna. Normaaliin tapaan käyttäjän antama syöte yritetään muuttaa kokonaisluvuksi int()-tyypinmuunnoksella. Jos se ei onnistu, siirrytään except-osaan, jossa tulostetaan käyttäjälle virheilmoitus. Virhe, joka aiheutuu siitä, että vääränlaista syötettä yritetään muuttaa luvuksi on tyypiltään ValueError. Sen vuoksi except-osa kirjoitetaan käsittelemään nimenomaan tämäntyyppinen virhe.

def main():
    NAULAKERROIN = 0.4536
    print("Muutan nauloina annetun massa kilogrammoiksi.")
    try:
        syote = input("Anna massa nauloina: ")
        naulat = int(syote)
        kilot = NAULAKERROIN * naulat
        print(f"Massa on {kilot:.3f} kg")
   except ValueError:
        print("Virhe - et antanut nauloja kokonaislukuna.")


main()

Esimerkki ohjelman suorituksesta silloin, kun käyttäjä antaa sopivan syötteen:

Muutan nauloina annetun massa kilogrammoiksi.
Anna massa nauloina: 45
Massa on 20.412 kg

Kaksi esimerkkiä suorituksesta silloin, kun käyttäjän syöte ei ole sopiva:

Muutan nauloina annetun massa kilogrammoiksi.
Anna massa nauloina: 45.8
Virhe - et antanut nauloja kokonaislukuna.
Muutan nauloina annetun massa kilogrammoiksi.
Anna massa nauloina: jotain tekstia
Virhe - et antanut nauloja kokonaislukuna.

Toisessa esimerkissä ei tyydytä pelkkään virheilmoitukseen. Siinä käyttäjältä pyydetään massaa uudelleen niin kauan, että tämä saadaan kokonaislukuna. Tämä saadaan aikaiseksi sijoittamalla try–except-rakenne toistokäskyn sisään. Toistokäskyä suoritetaan niin kauan, kunnes on saatu luetuksi kelvollinen kokonaisluku. Tätä tutkitaan muuttujan luku_onnistui avulla. Sen arvo on aluksi False, mutta se muutetaan True:ksi try-osan lopussa (joka suoritetaan vain siinä tapauksessa, että try-osan aikaisemmat käskyt on suoritettu ilman poikkeuksen aiheutumista).

def main():
    NAULAKERROIN = 0.4536
    print("Muutan nauloina annetun massa kilogrammoiksi.")
    luku_onnistui = False
    while not luku_onnistui:
        try:
            syote = input("Anna massa nauloina: ")
            naulat = int(syote)
            kilot = NAULAKERROIN * naulat
            print(f"Massa on {kilot:.3f} kg")
            luku_onnistui = True
        except ValueError:
            print("Virhe - et antanut nauloja kokonaislukuna.")
            print("Yrita uudelleen!")


main()
Tämän muuttujan avulla säädetään sitä, kuinka kauan käyttäjältä pyydetään uutta massaa. Muuttujalla pitää antaa alkuarvoksi False, jotta toistokäsky sisällä olevat käskyt suoritettaisiin 1. kerran.
Pyytämistä jatketaan niin kauan kuin muuttujan luku_onnistui arvo on False.
Jos tämän käskyn suorittaminen ei onnistu oikein, hypätään except-osaan.
Tähän käskyyn tullaan vain silloin, jos ei olla hypätty aikaisemmin samalla kierroksella except-osaan. Niinpä muuttujan luku_onnistui arvoksi vaihdetaan True vain siinä tapauksessa, että kokonaislukumuunnos on jo onnistunut.
Nämä käskyt suoritetaan vain siinä tapauksessa, että muunnos kokonaisluvuksi ei ole onnistunut.

Esimerkki ohjelman suorituksesta silloin, kun käyttäjä antaa heti sopivan syötteen:

Muutan nauloina annetun massa kilogrammoiksi.
Anna massa nauloina: 150
Massa on 68.040 kg

Toinen ajoesimerkki, jossa käyttäjä antaa ensin kaksi kertaa väärän syötteen:

Muutan nauloina annetun massa kilogrammoiksi.
Anna massa nauloina: 120.5
Virhe - et antanut nauloja kokonaislukuna.
Yrita uudelleen!
Anna massa nauloina: sotkua
Virhe - et antanut nauloja kokonaislukuna.
Yrita uudelleen!
Anna massa nauloina: 120
Massa on 54.432 kg