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ä
try
–except
-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()
luku_onnistui
arvo on
False
.except
-osaan.except
-osaan. Niinpä muuttujan
luku_onnistui
arvoksi vaihdetaan True
vain siinä tapauksessa,
että kokonaislukumuunnos on jo 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
False
, jotta toistokäsky sisällä olevat käskyt suoritettaisiin 1. kerran.