Hlavní navigace

Létající cirkus (8)

28. 3. 2002
Doba čtení: 8 minut

Sdílet

Chybové stavy byly vždy problémem. Různá protředí se s jejich existencí vyrovnala po svém. Všeobecně uznávaným a hojně používaným způsobem se staly výjimky. Dnešní díl seriálu o jazyce Python vám ukáže, kterak se s nimi vypořádat.

Chyby

Až po dnešní díl jsme se o chybových stavech příliš nebavili. Nyní nastává čas si vše objasnit. Jazyk Python rozlišuje dva druhy chybových stavů: syntaktické chyby a výjimky. Oba druhy chyb mají ale stejný důsledek, dojde k vyvolání výjimky.

Začněme tím, jak se výjimky projevují. Poté, co dojde k určitému chybovému stavu, může program vyvolat výjimku. Tuto výjimku následně může program odchytit a přizpůsobit se chybě, která nastala. Každá chyba – výjimka – je určena svým identifikátorem, který specifikuje, o jaký druh chyby se jedná (např. SyntaxError, AttributeError, OSError). Výjimky mohou mít i své argumenty, které upřesňují, k jaké chybě došlo (výjimka IOError má tři argumenty: errno – číslo chyby, filename – jméno souboru, jehož se chyba týká, a strerror – textový popis chyby). Nejsou-li učiněny žádné kroky k zachycení výjimky, šíří se z volané funkce do volajících funkcí. Není-li výjimka vůbec odchycena, interpretr vypíše chybové hlášení sestávající z výpisu volaných funkcí a z informací o výjimce, tj. o jejím druhu a doplňujících informací. Takový výpis může vypadat třeba takto:

Traceback (most recent call last):
  File "igui/events.py", line 40, in Process
    consumer(self)
  File "test.py", line 30, in pokus
    window.events.OnClose.sender = 'foo'
  File "igui/events.py", line 54, in __setattr__
    raise AttributeError, "read-only atribute '%s'" % name
AttributeError: read-only atribute 'sender'

V tomto bodě se liší syntaktické chyby od výjimek. Při vzniku syntaktické chyby je vypsáno chybové hlášení a interpretr ještě ukáže přímo místo chyby na příslušné řádce (v tomto případě chybějící dvojtečka v konstrukci for):

Traceback (most recent call last):
  File "<stdin>", line 1, in ?
  File "<string>", line 1
    for x in [1, 2, 3]
                     ^
SyntaxError: invalid syntax

Vznik výjimek

Výjimka je vyvolána konstrukcí raise. Za klíčovým slovem raise následuje identifikátor výjimky, případně ještě její argument. V Pythonu může být identifikátorem výjimky buď řetězec, nebo třída, nebo její instance. Nejjednodušší vyvolání výjimky může vypadat třeba takto:

>>> raise 'chyba vstupu/vystupu', {'soubor': '/home'}

V tomto případě je identifikátorem výjimky řetězec. Za tímto řetězcem může následovat i argument této výjimky. V počátcích jazyka byl toto jediný druh výjimek. Později k nim přibyla možnost používání tříd a jejich instancí. Je-li identifikátorem výjimky třída, nabízí se několik možností. Pokud je argument výjimky instance třídy, která byla použita jako identifikátor, případně instance třídy od ní odvozené, je jako výjimka použita tato instance. Není-li argumentem instance třídy, je z identifikátoru třídy vytvořena nová instance. Při vytváření jsou konstruktoru výjimky předány argumenty, které se odvodí z argumentů předaných konstrukci rasise. Pro bližší informace vás odkážu do dokumentace jazyka Python. Je-li konstrukci raise předána instance třídy, nemohou již být předány další argumenty výjimky.

Odchycení výjimek

Výjimka se po svém vzniku šíří volajícími funkcemi, dokud není odchycena konstrukcí try…except. Při spuštění této konstrukce se nejprve začne vykonávat blok mezi slovy try a except. Dojde-li v tomto bloku k výjimce, vykonávání se přeruší a začne se porovnávat identifikátor výjimky s tuple identifikátorů uvedených za slovem except (je-li identifikátor jen jeden, nemusí se jednat o tuple, můžeme ho zapsat přímo, viz. následující příklad). Je-li nalezena shoda, spustí se blok následující po except. Shodou se rozumí:

  • je-li identifikátor výjimky řetězec, ke shodě dojde, pokud se shoduje tento řetězec s řetězcem uvedený ze slovem except.
  • je-li identifikátor výjimky třída, ke shodě dojde, pokud je tato třída odvozená od třídy uvedené za slovem except (termín odvozená zahrnuje i rovnost, viz. předminulý díl, odstavec o funkci isinstance()).
  • je-li identifikátorem výjimky instance třídy, dojde ke shodě, je-li třída této instance předané odvozená od třídy uvedené za slovem except.

Bloků except může být uvedeno i více, každý s jiným identifikátorem výjimky (čili pro jiný druh chyby). Porovnávání identifikátorů výjimek se děje popořadě, tak, jak jsou jednotlivé větve except napsány ve zdrojovém kódu. Bloky except mohou být i pro společné identifikátory výjimky, ale po vykonání jednoho bloku except se již neprovádí další porovnávání identifikátorů výjimek a běh programu pokračuje za posledním blokem. Není-li nalezena odpovídající větev except, není výjimka odchycena a rozšíří se o úroveň výše v zásobníku volaných funkcí. Pokud není odchycena ani zde, šíří se výše atd., dokud nedojde k její odchycení. Jesliže se ocitneme na vrcholu zásobníku, o obsloužení výjimky se postará interpretr, který vypíše traceback a ukončí se.

Seznam identifikátorů za slovem except můžeme vynechat, potom tato větev odpovídá jakékoli výjimce. Je-li součástí konstrukce try…except více větví except, musí být větev odpovídající každé výjimce uvedena jako poslední. Musím ale upozornit, že tato konstrukce je velice hazardním úsekem kódu a je třeba ho používat s velkou obezřetností. Může totiž skrýt některé chyby, o jejichž výskytu nejsme informováni. Vyskytne-li se v takto chráněném kódu třeba chyba dělení nulou, je odchycena konstrukcí except a programátor se nic nedozví (pokud není tělo except napsáno tak, že vypíše své vlastní hlášení o chybě apod.).

Nyní si již ukážeme příklad použití výjimek:

>>> while 1:                                       # (1)
...     x = raw_input("Soubor nebo 'quit': ")      # (2)
...     try:                                       # (3)
...         if x == 'quit':                        # (4)
...             raise 'quit'                       # (5)
...         print open(x).read()                   # (6)
...     except IOError:                            # (7)
...         print 'Chybne jmeno souboru!'          # (8)
...     except 'quit':                             # (9)
...         break                                  # (10)
...

Trochu strojená ukázka, ale jako příklad postačí. V nekonečném cyklu si postupně necháme zadat jméno souboru. Na řádku (3) začíná chráněný blok kódu. Zde nejprve otestujeme, zdali proměnná x není rovna řetězci ‚quit‘. Je-li tomu tak, vyvoláme výjimku ‚quit‘, kterou má odchytit konstrukce except na řádku (9). Řádek (6) otevře soubor, který zadal uživatel, a vypíše jeho obsah na standardní výstup. Bylo-li zadáno chybné jméno souboru, vznikne ve funkci open() výjimka IOError, ta je odchycena konstrukcí except na řádku (7), a je vypsáno chybové hlášení. Veškeré výjimky, které používají vestavěné moduly a samotný interpretr jsou definovány v modulu exceptions a jsou automaticky zavedeny do vestavěného prostoru jmen modulu. Proto k nim můžeme přistupovat přímo uvedením jejich jména bez nutnosti importovat modul exceptions.

Argumenty výjimek

Těm všímavějším z vás jistě neuniklo, že náš program zatím nezískával žádné informace o příčinách výjimky. Příčiny, jak jsme si řekli na začátku dnešního dílu, nám prozradí argumenty výjimky. Argumenty jsou předávány konstrukci raise, které s jejich pomocí vytvoří instanci výjimky. Při výskytu výjimky hledá interpetr odpovídající except část, která, jak víme, je složena z typle identifikátorů výjimek. Za tímto tuple může ještě následovat jedna nepovinná část – libovolné jméno proměnné, kterému je výjimka přiřazena, odpovídá-li identifikátoru:

>>> while 1:                                       # (1)
...     x = raw_input("Soubor nebo 'quit': ")      # (2)
...     try:                                       # (3)
...         if x == 'quit':                        # (4)
...             raise 'quit'                       # (5)
...         print open(x).read()                   # (6)
...     except IOError, details:                   # (7)
...         print 'Chybne jmeno souboru:'          # (8)
...         print details.filename                 # (9)
...     except 'quit':                             # (10)
...         break                                  # (11)
...

Příklad je obdobný jako výše uvedený. Na řádku (7) ale ke jménu IOError přibylo ještě jméno details, kterému se v případě výskytu výjimky IOError přiřadí výjimka. Jelikož IOError je třída, proměnné details je přiřazena její instance. Z dokumentace zjistíme, že tato instance obsahuje několik atributů a jedním z nich je i filename, což je jméno souboru, jehož se výjimka týká. Každá výjimka může mít množství atributů, záleží na jejím typu (rozuměj její třídě). Pro více informací nahlédněte do dokumentace k Pythonu. Je-li identifikátorem výjimky řetězec, jsou detaily o výjimce rovny argumentům předaným konstrukci raise. Nejsou-li tyto argumenty uvedeny, je dosazena hodnota None.

Došlo-li k nějaké výjimce, je obvyklým postupem výjimku odchytit a zpracovat. Někdy však programátor chce při výskytu výjimky provést nějakou akci, ale nechce výjimku odchytit. Proto je zde varianta konstrukce raise, která bez parametrů vyvolá naposledy nastavenou výjimku. Klasické použití je při odchytávání všech výjimek větví except bez parametrů. Při výskytu výjimky ji program odchytí, posléze vytiskne chybové hlášení a výjimku obnoví:

>>> while 1:
...     x = raw_input("Soubor nebo 'quit': ")
...     try:
...         if x == 'quit':
...             raise 'quit'
...         print open(x).read()
...     except IOError, details:
...         print 'Chybne jmeno souboru:'
...         print details.filename
...     except 'quit':
...         break
...     except:
...         print 'Doslo k chybe!'
...         raise
...

Přejeme-li si výjimku odchytit, ale nechceme provést žádnou akci při jejím výskytu, můžeme použít konstrukci pass, která plní pouze funkci jakési výplně na místech, kde se očekává blok kódu.

Konstrukce try…except může mít ještě třetí nepovinnou část, blok else, který je vykonán při úspěšném vykonání těla bloku try. Výjimky, které vzniknou v části else, však již nejsou obslouženy bloky except na stejné úrovni.

Přístup k informacím o výjimce

Je-li použita část except bez tuple výjimek, není možné použít cílovou proměnnou, které se přiřadí výjimka. Z toho důvodu Python poskytuje další cestu, jak si zpřístupnit informace o výjimce. Tyto informace nám zprostředkovává modul sys pomocí následujících proměnných:

  • sys.exc_type – typ výjimky, defacto její identifikátor předaný konstrukci raise.
  • sys.exc_value – argument výjimky
  • sys.exc_traceback – traceback objekt, který obsahuje informace o volaných funkcích. Přístup k tomuto objektu je možný pomocí modulu traceback. Bližší informace viz dokumentace k jazyku.
  • sys.exc_info() – funkce, která vrací tuple (sys.exc_type, sys.exc_value, sys.exc_traceback).

Konstrukce try…finally

Další variantou klíčového slova try je konstrukce try…finally. Ta se používá při definování cleanup kódu (kódu, který je spouštěn vždy a používá se např. pro uzavření souborů). Při vykonávání této konstrukce se nejprve spustí blok následující po slově try. Při jeho opuštění se vykoná blok za klíčovým slovem finally, přičemž nezáleží na způsobu ukončení bloku try (může jím být použití konstrukce return pro návrat z funkce, nebo vznik výjimky).
Klíčové slovo finally nelze použít zároveň s except. Oba typy chráněných bloků však mohou být do sebe libovolně zanořovány.

Doplnění

V Pythonu existují výjimky, kterých se týkají určité zvláštnosti. Tou první je SyntaxError – syntaktická chyba, pokud se vyskytne na nejvyšší úrovni (tj. v hlavním modulu), nelze ji odchytit. Pokud se však vyskytne v některém z modulů, které importujeme, chová se jako obyčejná výjimka a lze ji odchytit klasickou konstrukcí try…except.

Další výjimkou, která má zvláštní význam, je výjimka KeyboardInterrupt. Ta může být vyvolána v kterémkoli místě programu stisknutím kombinace kláves Ctrl-C Chová se jako jakákoli jiná výjimka, a pokud není odchycena, způsobí stisk Ctrl-C ukončení programu.

CS24 tip temata

Poslední výjimkou, kterou si zde uvedeme, je SystemExit. Ta se opět chová jako jakákoli jiná výjimka s tím rozdílem, že pokud není odchycena, způsobí ukončení interpretru (i interaktivního), aniž by vytiskla hlášení o chybě.

Příště

V dnešním dílu jsme si probrali, jak se vypořádat s běhovými chybami v Pythonu. Příští díl bude věnovaný těm částem syntaxe, na které se zatím nedostalo. Bude se jednat hlavně o anonymní funkce – lambda funkce, skládání a rozklad sekvencí a formátování řetězců.