Uživatelské nástroje

Nástroje pro tento web


prog:chyby

Chyby a jejich ošetření

Z vlastní zkušenosti již dobře víte, že při programování (stejně jako při každé jiné činnosti) se dopouštíme chyb. Jde jednak o syntaktické chyby, které nám interpret Pythonu ihned ohlásí a vůbec nedovolí náš program spustit, jednak o chyby, které se projeví až při běhu programu. Výskyt chyby vede k okamžitému ukončení programu s výpisem informací o chybě. Cílem této kapitoly je ukázat možnosti, jak takové chyby v programu zachytit a včas na ně reagovat.

Druhy chyb

Představme si jednoduchý program1), kterým bychom chtěli demonstrovat matematickou poučku, že součin 1/x.x = 1.

def prevracena(x):
    return 1 / x # výpočet převrácené hodnoty
 
def tisk(x): # součin hodnoty a převrácené hodnoty
    prev_x = prevracena(x)
    print("{:.6f} * {:.6f} = {:.6f}".format(x, prev_x, x * prev_x))
 
# seznam testovacích prvků
prvky = [12.5, 0.625, 0.01, -5, 0, 3.18, 34.5, -0.005, 4]
while True:
    vstup = input("Zadej index: ")
    if not vstup: # prázdný vstup ukončuje program
        break
    index = int(vstup) # získání indexu prvku
    tisk(prvky[index]) # požadovaná operace s prvkem

Máme seznam testovacích prvků a v cyklu žádáme uživatele o zadání indexu prvku, se kterým provedeme kontrolní výpočet. Pro řadu vstupů (např. indexy 2, 5, 7) program pracuje správně, při některých však dojde k chybě.

Zadej index: 9
Traceback (most recent call last):
  File "exc.py", line 15, in <module>
    tisk(prvky[index]) # požadovaná operace s prvkem
IndexError: list index out of range

Zadej index: 4   
Traceback (most recent call last):
  File "exc.py", line 15, in <module>
    tisk(prvky[index]) # požadovaná operace s prvkem
  File "exc.py", line 5, in tisk
    prev_x = prevracena(x)
  File "exc.py", line 2, in prevracena
    return 1 / x # výpočet převrácené hodnoty
ZeroDivisionError: division by zero

Zadej index: a
Traceback (most recent call last):
  File "exc.py", line 14, in <module>
    index = int(vstup) # získání indexu prvku
ValueError: invalid literal for int() with base 10: 'a'

Z chybového výpisu se dozvíme:

  • druh chyby a její bližší popis - Kromě uvedených IndexError (chyba indexu), ZeroDivisionError (dělení nulou), ValueError (chybná hodnota) jste se již asi potkali také s TypeError (chybný typ např. ve výrazu len(21.05)), AttributeError při snaze použít neexistující metodu nějakého objektu např. "string".len() či NameError při snaze použít neexistující proměnnou.
  • místo vzniku chyby - Místo je určeno zdrojovým souborem2) a číslem řádku (pro lepší orientaci je příslušný řádek také vypsán). Pokud se nachází mimo hlavní program (tj. uvnitř nějaké funkce či metody), je uvedena celá hierarchie volání. V příkladu výše pro zadaný index 4 vidíme, že chyba nastala na řádku 2 ve funkci prevracena, která byla zavolána z funkce tisk na řádku 5, a tu vyvolal příkaz na řádku 15 v hlavním programu.

Jestliže jsme chyby objevili, budeme se snažit je opravit. V případě dělení nulou lze do funkce prevracena přidat podmínku, zda je argument nenulový, stejně tak není obtížné vyhnout se indexu mimo rozsah seznamu. Úplné ošetření vstupu je ale úloha obtížnější - uvažte například, že platné hodnoty pro zadání reálného čísla mohou být i INF, +infinity, NaN apod.

Výjimky a jejich zachycení

Situace, které nemáme zcela pod svou kontrolou, nezahrnují jen vstup od zlomyslného či neznalého uživatele. Nečekaně může dojít k selhání pevného disku v průběhu zápisu souboru, přerušení síťového připojení při načítání webové stránky apod. V těchto případech se ukazuje jako výhodný přístup, kdy připouštíme výskyt nějaké chyby, ale budeme se snažit ji zachytit, ošetřit a z chyby se zotavit.

def vstup_int():
    try:
        hodnota = int(input("Zadej celé číslo: "))
    except ValueError:
        hodnota = None
    return hodnota

Uvedený fragment programu představuje funkci pro vstup celého čísla. Za klíčovým slovem try následuje chráněný blok, v našem případě tvořený pouze jediným řádkem. Následuje sekce se zpracováním výjimek (anglicky exception) vyvolaných vznikem chyby. Větve pro jednotlivé druhy chyb jsou uvedeny klíčovým slovem except a druhem chyby. Pokud kdekoliv v chráněném bloku dojde k chybě, je vykonávání dalšího kódu v tomto bloku přerušeno (bez ohledu na hloubku vnoření, tj. počet cyklů, volaných funkcí atp.). Místo toho se vykoná blok obsluhy chyby daného druhu. Následně program normálně pokračuje3). Není-li obsluha chyby nalezena (tj. výjimka není zachycena), dojde k přerušení programu s výpisem chyby, jak jsme byli doposud zvyklí.

Konkrétně, pokud v uvedené funkci vstup_int dojde při pokusu o převod z řetězcové hodnoty na typ int k chybě ValueError, provede se v obsluze této chyby přiřazení hodnoty None do lokální proměnné hodnota. Ať již byl vstup platný nebo ne, je nakonec proveden příkaz return s aktuální návratovou hodnotou.

Smyčku hlavního programu ze začátku kapitoly bychom mohli upravit zavedením chráněného bloku s ošetřením výjimek.

while True:
    vstup = input("Zadej index: ")
    if not vstup: # prázdný vstup ukončuje program
        break
    try:
        # toto je chráněný blok
        index = int(vstup) # získání indexu prvku
        tisk(prvky[index]) # požadovaná operace s prvkem
    except ValueError:
        print("To není celé číslo.")
    except IndexError:
        print("Index mimo rozsah.")
    except ZeroDivisionError:
        print("Nulou nelze dělit.")
    else:
        print("V pořádku provedeno.")
    finally:
        print("Toto se provede úplně vždy.")

Povšimněte si několika větví zpracování výjimek podle druhu, za kterými mohou ještě následovat blok else (vykoná se, když chráněný blok je normálně dokončen bez vzniku chyby) a případně i finally (vykonán naprosto vždy, např. i pokud je v chráněném bloku příkaz return, při nezachycené výjimce atd.). Pokud poslední z větví except nemá přiřazen druh chyby, zpracují se zde všechny ostatní dosud neuvedené výjimky. Chráněný blok automaticky obslouží i kód uvnitř funkcí, které jsou zavolány z tohoto bloku.

Chráněné bloky mohou být i značně rozsáhlé a stejně jako ostatní struktury jazyka je možné je i navzájem zanořovat. Obvykle se doporučuje obsluhovat pouze takové výjimky, kde jsme schopni problém nějakým způsobem eliminovat či napravit.

Vlastní výjimky

Kromě zachycení výjimek různých druhů je možné také úmyslně vyvolat nějakou výjimku. Pokud budeme programovat funkci pro výpočet čtvrté odmocniny, pak při předání záporného argumentu můžeme pomocí příkazu

    raise ValueError("Argument musí být nezáporný")

vyvolat výjimku ValueError s příslušným popisem. Taková výjimka se chová naprosto stejně jako vestavěné výjimky, tj. může být zachycena v programu nebo má za důsledek jeho ukončení a je nakonec vypsána prostředím Pythonu. Někdy se raise využívá i v bloku obsluhy výjimky, kdy po provedení nějakých potřebných kroků se příznak chyby „pošle dál“ nadřízenému bloku k dalšímu zpracování.

Jazyk Python umožňuje i vytvářet nové druhy výjimek i řadit je v určité hierarchii. Tyto záležitosti jsou ale nyní mimo rámec našeho výkladu.

Výjimky a jejich zpracování představují poměrně mocný nástroj, který by však neměl být zneužíván k tomu, aby nahrazoval běžnou strukturu programu realizovanou cykly, podmíněnými příkazy, funkcemi atp. Kromě snížené přehlednosti (se všemi z toho plynoucími důsledky) by to mělo i výkonnostní dopad! Slouží právě jen k ošetření poměrně vzácně se vyskytujících stavů.

1)
Předesílám, že se jedná o nepříliš povedený program, který byste asi nenapsali.
2)
nesmíme zapomínat, že program se může skládat z více modulů
3)
ošetřená výjimka je anulována
prog/chyby.txt · Poslední úprava: 04.06.2018 23:32 autor: Ivana Stefanová