Arcade-pelien ohjelmointi
Pythonilla ja Pygamella

Chapter 18: Poikkeukset

Video: Exception Handling

Jos jotain menee ohjelmassa pieleen, ei Pythonin punainen virheilmoitus ei ole kovin mieluisaa nähtävää. Mitä, jos ohjelma tilttaa, miten saat suljettua ohjelman hallitusti? Tässä kohtaa tulee käyttöä poikkeuksille.

Poikkeuksilla voidaan käsitellä kaikki epätavalliset tilanteet ohjelman suorituksen aikana. Poikkeuksia käytetään tiedosto-operaatioissa ja verkkoliikenteen virhetilanteissa. Poikkeuksilla saadaan hallitusti käsiteltyä tallennustilan käsittelyvirheet, verkkoliikenteen virheet ja käyttöoikeusvirheet.

18.1 Sanastoa

Poikkeusten käsittelyyn liittyy muutamia käsitteitä ja termejä. Tässä muutama yleisesti käytössä oleva:


Useimmat ohjelmointikielet käyttävät termiä “throw” ja “catch.” Valitettavasti Pythonissa tätä ei käytetä. Python käyttää “raise” ja “exception” termejä. Käsitepari throw/catch on tässä mukana koska ne ovat käytössä yleisemmin käytössä olevissa ohjelmointikielissä.

18.2 Poikkeuksen käsittely

Poikkeuksen käsittelevä koodi on yksinkertainen. Siitä esimerkki alla:

# Divide by zero
try:
    x = 5 / 0
except:
    print("Error dividing by zero")

Rivillä kaksi on try lause. Jokainen sisennetty koodirivi tässä lohkossa kuuluu “try block” lohkoon. try lohkon alla voi olla myös sisentämätöntä koodia , jota ei aloiteta except lauseella. try osassa määritetään suoritettava ohjelmakoodi.

Jos ohjelmasta nousee poikkeus suorituksen aikana, siirtyy suoritus välittömästi “catch lohkoon.” Tämä lohko on sisennettynä except lauseen alle rivillä 4. Tämä kohta huolehtii virheen käsittelystä.

Ohjelmassa voidaan käsitellä poikkeuksella myös tekstityyppisen muuttujan muuntaminen lukutyyppiin. Esimerkiksi:

# Invalid number conversion
try:
    x = int("fred")
except:
    print("Error converting fred to a number")

POikkeus nostetaan rivillä 3, jossa virheellisesti merkkijono 'fred' muunnetaan kokonaisluvuksi. Rivillä 5 tulostetaan virheilmoitus.

Alla on laajempi esimerkki virheenkäsittelystä. Virheentarkistuksessa tutkitaan, että käyttäjä syöttää kelvollisen kokonaisluvun. Jos sopivaa kokonaislukua ei syötetä, pyydetään kelvollista lukua. Koodissa nostetaan poikkeus rivillä 5, jos käyttäjä syöttää epäkelvollisen kokonaisluvun. Poikkeus käsitellään except-lohkossa ja lukua pyydetään uudelleen. Rivillä 6 olevaa totuusarvoista asetusta number_entered arvoon True ei tehdä, jos suoritus keskeytyy poikkeukseen rivillä 5.

number_entered = False
while not number_entered:
    number_string = input("Enter an integer: ")
    try:
        n = int(number_string)
        number_entered = True
    except:
        print("Error, invalid integer")

Tiedostot ovat erityisen virheherkkiä tiedostotoiminnoissa. Levytila voi täyttyä, tiedosto voidaan poistaa samaan aikaan kuin sitä kirjoitetaan, tai se on voitu siirtää. USB-muisti on saatettu irrottaa virheellisesti kesken operaation. Tällaiset virheet voidaan helposti käsitellä poikkeusten käsittelyllä.

# Error opening file
try:
    my_file = open("myfile.txt")
except:
    print("Error opening file")

Monet eri virheet voidaan käsitellä monella eri tavalla. Tästä syystä myös virheilmoitukset voidaan muotoilla monipuolisemmin kuin yksinkertaisesti ilmoittamalla “an error has occurred.”

Alla olevassa koodissa on erilaisia virhetilanteita riveillä 5-8. Asettamalla IOError jälkeen except sanan rivillä 9, käsitellään ainoastaan Input/Output (IO) virheet tässä kohtaa. Samalla tapaa rivillä 11 käsitellään ainoastaan arvojen muunnoksissa tapahtuvat virheet, rivillä 13 huolehditaan mollalla jakovirheet. Rivillä 15 käsitellään yleisesti kaikki muut virheet, joita ei edellä käsitelty. “catch-all” except osan tulee olla aina viimeisenä.

Rivillä 1 ladataan sys kirjasto, jota tarvitaan rivillä 16 virheen tyypin tulostamiseen.

import sys

# Multiple errors
try:
    my_file = open("myfile.txt")
    my_line = my_file.readline()
    my_int = int(s.strip())
    my_calculated_value = 101 / my_int
except IOError:
    print("I/O error")
except ValueError:
    print("Could not convert data to an integer.")
except ZeroDivisionError:
    print("Division by zero error")
except:
    print("Unexpected error:", sys.exc_info()[0])

Sisäänrakennettujen virheilmoitusten kuvaukset voit lukea tältä nettisivulta:
http://docs.python.org/library/exceptions.html

18.3 Esimerkki: High Scoren tallennus

Tässä on esimerkki pelin 'high scoren' tallentamisesta tekstitiedostoon high_score.txt.

"""
Show how to use exceptions to save a high score for a game.

Sample Python/Pygame Programs
Simpson College Computer Science
http://programarcadegames.com/
http://simpson.edu/computer-science/
"""


def get_high_score():
    # Default high score
    high_score = 0

    # Try to read the high score from a file
    try:
        high_score_file = open("high_score.txt", "r")
        high_score = int(high_score_file.read())
        high_score_file.close()
        print("The high score is", high_score)
    except IOError:
        # Error reading file, no high score
        print("There is no high score yet.")
    except ValueError:
        # There's a file there, but we don't understand the number.
        print("I'm confused. Starting with no high score.")

    return high_score


def save_high_score(new_high_score):
    try:
        # Write the file to disk
        high_score_file = open("high_score.txt", "w")
        high_score_file.write(str(new_high_score))
        high_score_file.close()
    except IOError:
        # Hm, can't write it.
        print("Unable to save the high score.")


def main():
    """ Main program is here. """
    # Get the high score
    high_score = get_high_score()

    # Get the score from the current game
    current_score = 0
    try:
        # Ask the user for his/her score
        current_score = int(input("What is your score? "))
    except ValueError:
        # Error, can't turn what they typed into a number
        print("I don't understand what you typed.")

    # See if we have a new high score
    if current_score > high_score:
        # We do! Save to disk
        print("Yea! New high score!")
        save_high_score(current_score)
    else:
        print("Better luck next time.")

# Call the main function, start up the game
if __name__ == "__main__":
    main()

18.4 Poikkeuksen olio

Poikkeuksen nostamisessa saadaan aikaiseksi myös poikkeusolio. Tämä objekti saadaan käsiteltäväksi poikkeuksen nostossa as varatulla sanalla. Esimerkiksi:

try:
    x = 5 / 0
except ZeroDivisionError as e:
    print(e)

Muuttuja e sisältää tulostettavaa tietoa poikkeuksesta. Enemmänkin poikkeusolion avulla voitaisiin tehdä, mutta tässä kappaleessa ei asiaan syvennytä enempää. Lisää tietoa poikkeusoliosta saat vaikkapa Python-dokumentaatiosta.

18.5 Poikkeuksen generointi

Poikkeus voidaan luoda haluttuun tilanteeseen raise komennolla. Esimerkiksi:

# Generating exceptions
def get_input():
    user_input = input("Enter something: ")
    if len(user_input) == 0:
        raise IOError("User entered nothing")

getInput()

Kokeile yllä olevaa koodia ja muuta virheilmoitusta. Näin huomaat, miten IOError virheen nosto toimii.

Myös omien poikkeusten luominen on mahdollista, mutta tämäkin asia ei ole tämän materiaalin asia. Asiasta voit lukea enemmän seuraavalta nettisivulta:
http://docs.python.org/tutorial/errors.html#raising-exceptions

18.6 Proper Exception Use

Poikkeusten käsittelyä ei tarvita, varsinkaan jos tilanne voidaan hoitaa yksinkertaisesti if lauseella. Tavallisen toimivan ohjelmakoodin ei tulisi nostaa virheitä, varsinkin jos edetään niin sanotusti “onnellista polkua” virheitä välttäen. Hyvin konstruoidut 'try/catch' rakenteet on koodin luettaviuuden kannalta vielä hallittavissa, mutta koodi, jossa on useita eri virheenkäsittelijöitä ja hyppyjä eri kohtiin, saattaa olla yhtä painajaista debugata ja lukea.

18.7 Review

18.7.1 Multiple Choice Quiz

Klikkaa tästä monivalintatehtävään.

18.7.2 Short Answer Worksheet

Klikkaa tästä kappaleen kertaustehtäviin.


You are not logged in. Log in here and track your progress.