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í
- Python 3 Documentation: The
raise
statement - Python 3 Documentation: The
try
statement - PEP 3109: Raising Exceptions in Python 3000
- PEP 3134: Exception Chaining and Embedded Tracebacks
- PEP 409: Suppressing exception context
- PEP 415: Implement context suppression with exception attributes
- stackoverflow.com: What is the difference between
__cause__
and__context__
?