Erindite töötlemine

Erindeid on võimalik „kinni püüda“ ja määrata programmi edasine käitumine. Seda „kinni püüdmist“ nimetatakse erinditega tegelemiseks (ingl k exception handling). Erinditega tegelemiseks kasutatakse kahte koodiplokki, mille võtmesõnadeks on try ja except. Lausesse try lisatakse kood, mis võib tekitada viga. Vea tekkimisel käivitatakse tgevus, mis on kirjutatud except lausesse. Juhul kui viga ei teki, siis täidetakse try osas kogu kood. Konstruktsioonile võib ka lisada finally ploki, aga see ei ole kohustuslik.

Erindi kinni püüdmise struktuur:

try:
    # Try to to something, which may cause an error

except <Name of the error class>
    # If what you tried caused an error, this code block is executed
    # If the error was not thrown, this code block is ignored

finally:
    # This code block is executed anyway

Ülalolev näide on ümberkirjutatav järgnevale kujule:

some_word = "word"

try:
    # The ValueError is being thrown then executing this statement
    int(some_word)
    # After the error is thrown, the <except> code block is immediately executed
    print("This is not printed to the console")

# Catching the thrown error
except ValueError:
    print("This is printed to the console")

finally:
    print("This is printed anyway")

Vaatame veel mõnd näidet - oletame, et meil on olemas järjend, mille sees võivad olla täiesti juhuslikud elemendid (arvud, sõned, teised järjendid jne). Meie tahame arvutada selle järjendi sees olevate arvude summat nii, et kui järjendi elemendiks on mõni teine andmetüüp, ei lähe programm katki, vaid trükitakse konsooli vastavasisuline info.

black_box = ["fly", 42, 13, "bird", ["sub", "list"], "dinosaur", 24]

sum = 0

for element in black_box:
    try:
        # Try to add element to the sum
        sum += element

    except TypeError:
        # If it is not a number, print the message and continue the loop
        print(f"'{element}' is not a number!")

print(sum)

Saame koodi käivitamisega järgneva tulemuse, kus 79 on kõikide järjendis olevate arvude summa:

'fly' is not a number!
'bird' is not a number!
'['sub', 'list']' is not a number!
'dinosaur' is not a number!
79

Erindite kinni püüdmine võimaldab lahendada selliseid määramatust sisalduvaid probleeme väga elegantselt. Erindite kinni püüdmine on asendamatu näiteks kasutaja sisendi töötlemisel.

Vaatame veel üht näidet: olgu meil järjend erinevate mängude nimedega. Me tahame luua programmi, mis küsib kasutajalt arvu ja tagastab vastava indeksiga mängu järjendist. Kõige lihtsam implementatsioon on järgmine:

games = ["Minecraft", "GTA V", "Soma", "Heavy Rain", "Fahrenheit", "Witcher 3", "Skyrim"]

input_index = input(f"Type the number (0-{len(games) - 1}) to get a game to play: ")  # Ask for input number

game = games[int(input_index)]  # Get the element from list with the input index

print(f"And you should play '{game}'")

See on nö straight-forward lahendus, mis ei ole eriti paindlik. Kui kasutaja sisestab sõne, tühiku või üldse midagi muud, mis ei ole arv, läheb meie programm katki. Samamoodi läheb ta katki siis, kui sisestatud arv ei sobi indeksiks (on liiga suur või väike). Ja kui eksisteerib olukord, mille korral meie programm läheb katki, siis see kood ei ole hea ja seda programmi tuleks parandada. Antud näite puhul võivad tekkida kaks erinevat viga: kui kasutaja ei sisesta arvu või kui sisestatud arv on liiga suur/väike.

Kui me prooviksime teha vigade ennustamist ilma erindite kinni püüdmiseta, siis peaksime iga veaohtlikku olukorda eraldi vaatlema.

games = ["Minecraft", "GTA V", "Soma", "Heavy Rain", "Fahrenheit", "Witcher 3", "Skyrim"]

input_index = input(f"Type the number (0-{len(games) - 1}) to get a game to play: ")  # Ask for input number

# If input value is not a number
if not input_index.isdigit():
    print("This is not a number!")
    raise SystemExit  # Need to raise SystemExit for our program to stop

# If the input value is not a valid index
if int(input_index) > len(games) - 1 or int(input_index) < 0:
    print("This is not a valid index!")
    raise SystemExit

# If we have not raised SystemExit before, we will face the error anyway
game = games[int(input_index)]  # Get the element from list with the input index

print(f"And you should play '{game}'")

Tingimuslause kasutamine antud juhul eeldab, et me määrame iga võimaliku olukorra, mille korral meie programm võib minna katki. Kuna neid olukordi võib olla väga palju, siis see lahendus ei ole eriti hea, koodi võib tekkida ülemäära palju ning seda on keeruline lugeda. Näiteks teine tingimuslause iseloomustab ühte konkreetset erindi klassi (IndexError) ja on asendatav lausega except IndexError, mis on palju arusaadavam.

Kirjutame koodi ümber, kasutades erindite püüdmiseks try-except plokki:

games = ["Minecraft", "GTA V", "Soma", "Heavy Rain", "Fahrenheit", "Witcher 3", "Skyrim"]

input_index = input(f"Type the number (0-{len(games) - 1}) to get a game to play: ")

try:
    game = games[int(input_index)]
    print(f"And you should play '{game}'")
except ValueError:
    print("This is not a number!") # If the input value is not a number
except IndexError:
    print("This is not a valid number!") # If the input number is not a valid index

Nüüd on meie kood palju paindlikum ja ilusam, ühtlasi on väiksem tõenäosus, et koodi käivitamisena tekib programmi töös viga.