Subsections

 
8. Chyby a výjimky

V předchozím výkladu jsme mlčky přehlíželi jednu důležitou skutečnost - žádný program není bez chyby. Pokud uvažujeme chyby programátora, může jít o syntaktické chyby (většinou překlepy, špatně napsaný zdrojový kód) nebo chyby v logice programu (špatný návrh programu, chybné používání funkcí atd.). Syntaktické chyby lze poměrně snadno odstranit, chyby v logice však nikoli. Pokud již program pracuje jak má, stále na něj působí jakési vnější vlivy (operační systém, ostatní programy, omezení vyplývající z hardware apod.). Pokud se třeba program snaží zapisovat na disk a přitom je tento disk zaplněn, běhové prostředí Pythonu mu to sdělí pomocí výjimek. Právě jim proto bude věnována převážná část této kapitoly.

 
8.1 Syntaktické chyby

Pokud jste si zkoušeli jednotlivé příklady v této knize, určitě jste na nějaké syntaktické chyby narazili. Syntaktické chyby vznikají při zápisu programu, příkladem budiž následující řádka kódu:

>>> while 1 print 'Ahoj, světe'
  File "<stdin>", line 1, in ?
    while 1 print 'Ahoj, světe'
                ^
SyntaxError: invalid syntax

Jistě jste si všimli chybějící dvojtečky za příkazem while. Pokud se takto zapsaný kód pokusíte předat interpretru, pak ho vyhodnotí8.1 jako chybně zapsaný a vyvolá chybu (výjimku).

Důsledkem výjimky je vytisknutí chybové hlášky, kterou vidíte na předchozí ukázce. Všimněte si především malé "šipky", která ukazuje na místo, kde byla chyba detekována (tj. na příkaz print). Výskyt chyby lze předpokládat v těsném okolí této šipky (v našem případě před příkazem print). Součástí chybového výpisu je také jméno souboru a číslo řádky, takže přesně víte, kde chybu hledat.

 
8.2 Výjimky

Pokud je příkaz nebo výraz syntakticky správně, pokusí se ho interpretr spustit. I v této fázi však může dojít k chybě, proto interpretr podporuje mechanismus výjimek. Pomocí nich lze tyto chybové stavy indikovat a zajistit tak nápravu. Každá výjimka nejprve vznikne, je vyvolána nějakým příkazem. Po vyvolání výjimky je ukončen aktuální příkaz a výjimka se z tohoto příkazu šíří do funke, která tento příkaz spustila, z této funkce do funkce, která jí volala atd. stále výše v zásobníku volaných funkcí. Každá funkce může výjimku odchytit, čímž zastaví její šíření a může jí zpracovat. Pokud žádná funkce výjimku neodchytí (tzv. neodchycená výjimka), odchytí ji interpretr, ukončí program a vytiskne chybové hlášení:

>>> 10 * (1/0)
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
ZeroDivisionError: integer division or modulo
>>> 4 + spam*3
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
NameError: spam
>>> '2' + 2
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: illegal argument type for built-in operation

Jak vidíte v ukázce, chybových hlášení existuje mnoho. Každé indikuje, k jakému druhu chyby došlo, na jakém místě v programu a krátký popis chyby. V příkladu výše najdeme následující výjimky: ZeroDivisionError (dělení nulou), NameError (neexistující jméno proměnné) a TypeError (chybný datový typ). Všem těmto výjimkám odpovídají interní proměnné, narozdíl od jiných jazyků, kde v některých případech jde o klíčová slova! Popis chyby se liší v závislosti na typu výjimky. Většinou blíže specifikuje okolnosti chyby.

Součástí chybového výpisu je i výpis zásobníku volaných funkcí. Tak lze chybu snadno lokalizovat, určit její příčiny a zajistit nápravu. Jako součást výpisu funkcí jsou vypsánu i čísla řádek, jména souborů a samotný řádek, na kterém k chybě došlo. Detailní popis všech interních výjimek hledejte v Python Library Reference, části, zabývající se modulem exceptions.

 
8.3 Obsluhování výjimek

Každý blok kódu v programu může odchytit libovolnou výjimku, která vznikne v jeho těle. Ukažme si příklad:

>>> while 1:
...     try:
...         x = int(raw_input("Zadejte celé číslo: "))
...         break
...     except ValueError:
...         print "Nebylo zadáno správně, zkuste to znovu..."
...

Jistě jste pochopili, že jde o nekonečný cyklus, který se dotazuje uživatele na celé číslo (funkce raw_input()), které následně převeden na celé číslo (funkce int()). "Vtip" je v tom, pokud funkce int() dostane špatný argument (nepůjde o celé číslo, např. "1.0" nebo "nula" atd.), vyvolá výjimku ValueError.

U tohoto programu se setkáme ještě s jednou výjimkou. I když to není na první pohled zřejmé, může v libovolném místě programu vzniknout výjimka KeyboardInterrupt. Ta indikuje přerušení od uživatele (tj. stisk kombinace kláves Control-C). Tato výjimka odchycena nebude, tudíž cyklus se ukončí.

Možná jste si všimli nového příkazu try, který pracuje následovně:

Jak jsme si řekli o pár odstavců výše, může mít příkaz try více než jednu větev except, každou definovanou pro jiné výjimky. V každém případě ale bude spuštěn vždy nejvýše jeden (tj. buď žádný nebo jeden) handler obsluhující výjimku. Pokud u dvou různých větví except budou uvedeny stejné typy výjimek, Python to nevyhodnotí jako chybu, ale je třeba dát pozor, že ta větev, která je ve zdrojovém souboru uvedena jako druhá nebude na tuto výjimku reagovat!

Větev except může specifikovat více jak jednu výjimku formou tuple8.2 několika typů výjimek.

... except (RuntimeError, TypeError, NameError):
...     pass

U poslední větve můžeme dokonce vynechat výčet výjimek úplně. Pak tento handler funguje jako žolík - zpracuje všechny výjimky, které nebyly odchyceny předchozími větvemi. Ovšem i zde platí známé "všeho s mírou", protože unáhleným použitím except bez výpisu výjimek můžeme skrýt nějakou chybu kódu, se kterou jsme předem nepočítali.

Použití samotného except se přímo nabízí v případě, kdy chceme výjimku sice odchytit, následně vypsat nějaké chybové hlášení a výjimku znovu pustit do světa (více o vyvolání výjimek se dozvíte níže):

import string, sys

try:
    f = open('myfile.txt')
    s = f.readline()
    i = int(string.strip(s))
except IOError, (errno, strerror):
    print "I/O error(%s): %s" % (errno, strerror)
except ValueError:
    print "Nemohu převést data na celé číslo."
except:
    print "Neznámá chyba:", sys.exc_info()[0]
    raise

Podobně jako jiné příkazy, i příkaz try má volitelnou větev else. Ta se zapisuje po všech handlerech a její kód je spuštěn v případě, kdy žádná výjimka nenastala:

for arg in sys.argv[1:]:
    try:
        f = open(arg, 'r')
    except IOError:
        print 'nemohu otevřít', arg
    else:
        print arg, 'obsahuje', len(f.readlines()), 'řádků'
        f.close()

Pokud nějaká výjimka vznikla, pak může mít přiřazenu nějakou hodnotu nazývanou argument výjimky. Jeho typ a význam se řídí typem výjimky. Pokud má nějaká výjimka přiřazen argument, můžeme uvést ve větvi except za jménem výjimky i jméno proměnná, které se v případě odchycení výjimky tímto handlerem přiřadí argument výjimky. Podle tohoto argumentu můžeme rozhodnout o příčinách chyby a zajistit následnou akci programu na její odstranění:

>>> try:
...     spam()
... except NameError, x:
...     print 'jméno', x, 'není definováno'
... 
jméno spam není definováno

V případě, kdy výjimka má nějaký argument, je tento použit jako popis výjimky (detail) v chybovém hlášení interpretru.

Jelikož se výjimka šíří v zásobníku volaných funkcí vždy směrem vzhůru, odchytává příkaz try nejen výjimky vzniknuvší v bloku "jeho" kódu, ale i ve všech funkcích, které volá. Například:

>>> def chyba():
...     x = 1/0
... 
>>> try:
...     chyba()
... except ZeroDivisionError, detail:
...     print 'Běhová chyba:', detail
... 
Běhová chyba: integer division or modulo

 
8.4 Vyvolání výjimek

Příkaz raise umožňuje programově vyvolat výjimku určitého typu, přičemž lze specifikovat i argument výjimky:

>>> raise NameError, 'Ahoj'
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
NameError: Ahoj

V našem příkladě bylo jméno výjimky NameError a její argument řetězec "Ahoj". Je zvykem všechny výjimky nazývat různými variacemi na téma Error, např. TypeError, SyntaxError, ImportError a další. Význam a typ argumentu je, jak jsme si již řekli, závislý na typu výjimky.

V případě, o němž jsme hovořili výše (viz except bez výčtu výjimek) použijeme příkaz raise bez argumentů. V takovém případě vyvolá poslední výjimku, ke které došlo, tedy:

>>> try:
...     raise NameError, 'Ahoj'
... except NameError:
...     print 'Došlo k výjimce!'
...     raise
...
Došlo k výjimce!
Traceback (most recent call last):
  File "<stdin>", line 2, in ?
NameError: Ahoj

 
8.5 Výjimky definované uživatelem

Program může definovat svoje vlastní výjimky jako třídy odvozené od třídy Exception (více o třídách a objektovém programování se dozvíte z následující kapitoly), např.:

>>> class MyError(Exception):
...     def __init__(self, value):
...         self.value = value
...     def __str__(self):
...         return `self.value`
... 
>>> try:
...     raise MyError(2*2)
... except MyError, e:
...     print 'Vyskytla se výjimka s hodnotou:', e.value
... 
Vyskytla se výjimka s hodnotou: 4
>>> raise MyError, 'oops!'
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
__main__.MyError: 'oops!'

Třída výjimky může být definována i jako potomek jiné třídy než Exception, v praxi se to ale nepoužívá. Výjimka je plnohodnotnou třídou, může definovat množství atributů a metod. V praxi těchto možností ale příliš nevyužijeme, výjimky by totiž měly být jednoduché třídy sloužící hlavně k informování programu o chybě a její příčině.

Pokud píšete nějaký modul, který používá množství výjimek, je vhodné definovat společného předka těchto výjimek, třeba s názvem Error a teprve od něj odvozovat všechny ostatní chyby. Pokud totiž uživatel vašeho modulu bude chtít odchytit všechny výjimky, které mohou ve vašem modulu vzniknout, může napsat jedinou větev except jméno_modulu.Error. Například:

class Error(Exception):
    """Základní třída všech výjimek v tomto modulu."""
    pass

class InputError(Error):
    """Výjimky vyvolané pro chyby vstupu.

    Atributy:
        expression -- vstupní výraz, ve kterém došlo k chybě
        message -- popis chyby
    """

    def __init__(self, expression, message):
        self.expression = expression
        self.message = message

class TransitionError(Error):
    """Vyvolána, když se program pokusí o nepovolenou změnu stavu modulu. 

    Atributy:
        previous -- stav před změnou
        next -- nový stav
        message -- vysvětlení, proč daná změna není povolena
    """

    def __init__(self, previous, next, message):
        self.previous = previous
        self.next = next
        self.message = message

Mnoho standardních modulů Pythonu definuje vlastní třídy výjmek, jejichž hiearchie je podobná výše uvedené. Pokud jste zde uvedenému výkladu tříd neporozuměli, nelámejte si s tím hlavu. Přečtěte si následující kapitolu věnovanou objektově orientovanému programování a pak se k této části vraťte.

 
8.6 Definování clean-up akci

Příkaz try může mít místo větví except jedinou větev finally. Ta se používá pro tzv. clean-up akce, tj. kód, který by měl být spuštěn za všech podmínek a který se stará o korektní ukončení těla příkazu try (např. zavře otevřené soubory nebo smaže dočasná data). Ukázkovou clean-up akci, reprezentovanou příkazem print vidíte v následujícím příkladě:

>>> try:
...     raise KeyboardInterrupt
... finally:
...     print 'Mějte se, lidi!'
... 
Mějte se, lidi!
Traceback (most recent call last):
  File "<stdin>", line 2, in ?
KeyboardInterrupt

Větev finally je spuštěna za všech okolností, pokud v bloku chráněném příkazem try dojde k výjimce, je nejprve vykonána větev finally a až poté je standardním způsobem vyvolána výjimka. Clean-up akce je však spuštěna i v případě, kdy k žádné výjimce nedošlo a blok je opuštěn normálním způsobem, případně pomocí příkazu return apod.

Častou chybou začátečníků je pokus zkombinovat větve except a finally. Příkaz try vždy musí mít buď pouze větve except nebo pouze větve finally, v žádném případě ne obě zároveň. Pokud chcete výjimku odchytit a zároveň definovat nějakou clean-up akci, použijte dva do sebe vložené příkazy try, přičemž jeden bude definovat větve except a druhý větev finally.



Footnotes

... vyhodnotí8.1
Syntaxe programu se zpracovává v části nazývané parser, proto se také syntaktickým chybán někdy říká chyby při parsování.
... tuple8.2
Připomeňme, že při zápisu tuple, v případě, kdy nemůže dojít k dvojznačnostem, můžeme vynechat kulaté závorky.
Viz O tomto dokumentu... kde naleznete informace, jak upozornit na případné chyby.