Jste zde

Méně známé skutečnosti o Pythonu: konstrukce "raise from"

Když chcete v Pythonu vyhodit výjimku, napíšete raise Exception. Od Pythonu 3 existuje méně známé rozšířená příkazu raise obsahující dodatek from AnotherException. Právě o tomto rozšíření pojednává následující příspěvek.

Klasický raise a jeho chování

Mějme následující ukázku, převzatou s menšími úpravami z oficiální dokumentace k raise:

try:
    1 / 0
except Exception as e:
    raise RuntimeError('something bad happened')

Když kód spustíte, bude jeho výstupem

Traceback (most recent call last):
  File "./test.py", line 4, in <module>
    1 / 0
ZeroDivisionError: division by zero
 
During handling of the above exception, another exception occurred:
 
Traceback (most recent call last):
  File "./test.py", line 6, in <module>
    raise RuntimeError('something bad happened')
RuntimeError: something bad happened

Odchytili jsme výjimku ZeroDivisionError a vyhodili jsme jinou výjimku (RuntimeError). Python si v takové situaci automaticky poznačí původní výjimku, kterou pak vypíše, pokud nově vyhozenou výjimku nezpracujeme. Lze tak z výstupu zjistit, kterou výjimku jsme zpracovávali, když došlo k vyhození nové výjimky, což se může hodit při ladění.

Pro zvídavé: technicky je to řešeno tak, že se původně zpracovávaná výjimka uloží do atributu __context__ nově vyhozené výjimky (PEP 3134). Na tento atribut se pak dívá vypisovač neodchycených výjimek a vypíše ono "During handling of the above exception, another exception occurred".

raise from AnotherException

Může nastat situace, kdy chceme odchytit výjimku a místo ní vyhodit jinou výjimku s tím, že původní výjimka je přímým důsledkem nové výjimky. K tomu se hodí rozšíření from příkazu raise, dostupné od Pythonu 3. Příklad:

try:
    1 / 0
except Exception as e:
    raise RuntimeError('something bad happened') from e

Výstup:

Traceback (most recent call last):
  File "./test.py", line 4, in <module>
    1 / 0
ZeroDivisionError: division by zero
 
The above exception was the direct cause of the following exception:
 
Traceback (most recent call last):
  File "./test.py", line 7, in <module>
    raise RuntimeError('something bad happened') from e
RuntimeError: something bad happened

Je nám sděleno, že původní výjimka je přímým důsledkem nové výjimky (všimněte si odlišné chybové zprávy "The above exception was the direct cause of the following exception"). Není už to pouze vágní "při zpracování výjimky XXX došlo k vyhození YYY", ale máme přímo řečeno, která výjimka způsobila kterou. Lze takto řetězit i více výjimek za sebou.

Pro zvídavé: technicky je to řešeno tak, že se původně zpracovaná výjimka uloží opět do atributu nové výjimky, ale tentokrát se jedná o atribut __cause__, nikoliv __context__, jako tomu bylo v předchozím případu (PEP 3134).

raise from None

Poslední situace, o které si dnes povíme je, když chceme na základě nějaké výjimky vyhodit jinou s tím, že původní výjimku zatajíme, aby se uživateli vůbec nezobrazila. Využijeme k tomu konstrukci raise Exception from None:

try:
    1 / 0
except Exception as e:
    raise RuntimeError('something bad happened') from None

Výstup:

Traceback (most recent call last):
  File "./test.py", line 8, in <module>
    raise RuntimeError('something bad happened') from None
RuntimeError: something bad happened

Jak je vidět, informace o původní výjimce není dostupná. Toto se hodí, pokud je původní výjimka interní, o které se uživateli nechceme zmiňovat.

Pro zvídavé: technicky je to řešeno tak, že se do atributu __suppress_context__ nově vyhozené výjimky nastaví True (PEP 415).

Další čtení

Přidat komentář