Pojďme si prohloubit znalosti o chybách, neboli odborně o výjimkách (angl. exceptions).
Vezměme následující funkci:
def nacti_cislo():
odpoved = input('Zadej číslo: ')
return int(odpoved)
Když uživatel nezadá číslice, ale třeba text cokolada
,
nastene výjimka jménem ValueError
(chyba hodnoty) a Python vypíše
odpovídající chybovou hlášku.
Traceback (most recent call last):
File "ukazka.py", line 3, in nacti_cislo
cislo = int(odpoved)
ValueError: invalid literal for int() with base 10: 'cokolada'
Program volá funkci int()
pro neco, co nedává smysl jako číslo.
Co s tím má chudák funkce int
dělat?
Není žádná rozumná hodnota, kterou by mohla vrátit.
Převádění tohoto textu na celé číslo nedává smysl.
Až funkce nacti_cislo
nejlíp „ví“, co se má stát, když uživatel nezadá
číslice.
Stačí se uživatele zeptat znovu!
Kdybychom měli funkci, která zjistí jestli jsou v řetězci jen číslice,
mohlo by to fungovat nějak takhle:
def nacti_cislo():
while True:
odpoved = input('Zadej číslo: ')
if obsahuje_jen_cislice(odpoved):
return int(odpoved) # máme výsledek, funkce končí
else:
print('To nebylo číslo!')
# ... a zeptáme se znovu -- cyklus `while` pokračuje
Kde ale vzít funkci obsahuje_jen_cislice
?
Nechceme ji psát znovu – funkce int
sama nejlíp pozná, co se dá převést na
číslo a co ne.
A dokonce nám to dá vědět – chybou, kterou můžeme zachytit.
Pro zachycení chyby má Python příkaz try
/except
.
def nacti_cislo():
while True:
odpoved = input('Zadej číslo: ')
try:
return int(odpoved)
except ValueError:
print('To nebylo číslo!')
Jak to funguje?
Příkazy v bloku uvozeném příkazem try
se normálně provádějí, ale když
nastane uvedená výjimka, Python přeskočí zbytek bloky try
a provede všechno
v bloku except
.
Pokud výjimka nenastala, přeskočí se celý blok except
.
A co je to ValueError
? To je typ chyby.
Podobných typů je spousta.
Všechny jsou popsané v dokumentaci; pro nás jsou (nebo budou) důležité tyto:
BaseException
├── SystemExit vyvolána funkcí exit()
├── KeyboardInterrupt vyvolána po stisknutí Ctrl+C
╰── Exception
├── ArithmeticError
│ ╰── ZeroDivisionError dělení nulou
├── AssertionError nepovedený příkaz `assert`
├── AttributeError neexistující atribut/metoda, např. 'abc'.len
├── ImportError nepovedený import
├── LookupError
│ ╰── IndexError neexistující index, např. 'abc'[999]
├── NameError použití neexistujícího jména proměnné
│ ╰── UnboundLocalError použití proměnné, která ještě nebyla nastavená
├── SyntaxError špatná syntaxe, program je nečitelný/nepoužitelný
│ ╰── IndentationError špatné odsazení
│ ╰── TabError kombinování mezer a tabulátorů v odsazení
├── TypeError špatný typ, např. len(9)
╰── ValueError špatná hodnota, např. int('xyz')
Když odchytáváš obecnou výjimku,
chytnou se i všechny podřízené typy výjimek –
například except ArithmeticError:
zachytí i ZeroDivisionError
.
A except Exception:
zachytí všechny výjimky, které běžně chceš zachytit.
Většinu chyb není potřeba ošetřovat.
Nastane-li nečekaná situace, je téměř vždy mnohem lepší program ukončit, než se snažit pokračovat dál počítat se špatnými hodnotami. Navíc chybový výstup, který Python standardně připraví, může hodně ulehčit hledání chyby.
Zachytávej tedy jenom ty chyby, které očekáváš – víš přesně, která chyba může nastat a proč, a máš možnost správně zareagovat.
V našem příkladu to platí pro ValueError
z funkce int
.
Co ale dělat, kdyš uživatel chce ukončit program a zmáčkne
Ctrl+C?
Nebo když se mu porouchá klávesnice a selže funkce input
?
V obou případech je nejlepší reakce ukončit program a informovat
programátora, že (a kde) je něco špatně.
Neboli vypat chybovou hlášku – to, co se stane normálně, bez try
.
try
Pro úplnost: kromě except
existují dva jiné bloky,
které můžeš „přilepit“ k try
, a to else
a finally
.
První se provede, když v try
bloku
žádná chyba nenastane; druhý se provede vždy – ať
už chyba nastala nebo ne.
Můžeš taky použít více bloků except
. Provede se vždy maximálně jeden:
ten první, který danou chybu umí ošetřit.
try:
neco_udelej()
except ValueError:
print('Tohle se provede, pokud nastane ValueError')
except NameError:
print('Tohle se provede, pokud nastane NameError')
except Exception:
print('Tohle se provede, pokud nastane jiná chyba')
# (kromě SystemExit a KeyboardInterrupt, ty chytat nechceme)
except TypeError:
print('Tohle se neprovede nikdy')
# ("except Exception" výše ošetřuje i TypeError; sem se Python nedostane)
else:
print('Tohle se provede, pokud chyba nenastane')
finally:
print('Tohle se provede vždycky; i pokud v `try` bloku byl např. `return`')
Občas se stane, že výjimku budeš potřebovat vyvolat sama.
Často se to stává když píšeš nějakou obecnou funkci.
Třeba funkci na výpočet obsahu čtverce.
Co se stane, když někdo zavolá obsah_ctverce(-5)
?
-5
uživatel, je potřeba mu vynadat a zeptat se znovu.-5
nějaký robotický aparát, je potřeba ho líp zkalibrovat.-5
v nějakém výpočtu, je nejspíš potřeba opravit
chybu v tom výpočtu.Samotná funkce obsah_ctverce
ale „neví“, proč ji někdo volá.
Jejím úkolem je jen něco spočítat.
Měla by být použitelná ve všech případech výše – a v mnoha dalších.
Když někdo zavolá obsah_ctverce(-5)
, neexistuje správný výsledek, který by
funkce mohla vrátit.
Místo vracení výsledku musí tato funkce signalizovat chybu.
S tou se pak může program, který obsah_ctverce(-5)
zavolal,
vypořádat – vynadat uživateli, zkalibrovat měřák, nebo, pokud na chybu není
připravený, sám skončit s chybou (a upozornit tak programátora, že je něco
špatně).
Jak na to prakticky?
Chybu můžeš vyvolat pomocí příkazu raise
.
Za příkaz dáš druh výjimky a pak do závorek nějaký popis toho, co je špatně.
def obsah_ctverce(strana):
if strana > 0:
return strana * strana
else:
raise ValueError(f'Strana musí být kladná, číslo {strana} kladné není!')
Podobně jako return
, i příkaz raise
ukončí funkci.
A nejen tu – pokud na tuhle konkrétní chybu program předem připravený,
ukončí se celý program.
Ze začátku není příliš důležité dumat nad tím, který typ výjimky je ten
správný.
Klidně „střílej od boku“.
ValueError
bývá často správná volba.
{ "data": { "sessionMaterial": { "id": "session-material:2019/brno-podzim-pondeli:file:1", "title": "Výjimky", "html": "\n \n \n\n <h1>Výjimky</h1>\n<p>Pojďme si prohloubit znalosti o chybách, neboli odborně o <em>výjimkách</em>\n(angl. <em>exceptions</em>).</p>\n<p>Vezměme následující funkci:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"k\">def</span> <span class=\"nf\">nacti_cislo</span><span class=\"p\">():</span>\n <span class=\"n\">odpoved</span> <span class=\"o\">=</span> <span class=\"nb\">input</span><span class=\"p\">(</span><span class=\"s1\">'Zadej číslo: '</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"nb\">int</span><span class=\"p\">(</span><span class=\"n\">odpoved</span><span class=\"p\">)</span>\n</pre></div><p>Když uživatel nezadá číslice, ale třeba text <code>cokolada</code>,\nnastene výjimka jménem <code>ValueError</code> (chyba hodnoty) a Python vypíše\nodpovídající chybovou hlášku.</p>\n<div class=\"highlight\"><pre><span></span><span class=\"gt\">Traceback (most recent call last):</span>\n File <span class=\"nb\">"ukazka.py"</span>, line <span class=\"m\">3</span>, in <span class=\"n\">nacti_cislo</span>\n <span class=\"n\">cislo</span> <span class=\"o\">=</span> <span class=\"nb\">int</span><span class=\"p\">(</span><span class=\"n\">odpoved</span><span class=\"p\">)</span>\n<span class=\"gr\">ValueError</span>: <span class=\"n\">invalid literal for int() with base 10: 'cokolada'</span>\n</pre></div><p>Program volá funkci <code>int()</code> pro neco, co nedává smysl jako číslo.\nCo s tím má chudák funkce <code>int</code> dělat?\nNení žádná rozumná hodnota, kterou by mohla vrátit.\nPřevádění tohoto textu na celé číslo nedává smysl.</p>\n<p>Až funkce <code>nacti_cislo</code> nejlíp „ví“, co se má stát, když uživatel nezadá\nčíslice.\nStačí se uživatele zeptat znovu!\nKdybychom měli funkci, která zjistí jestli jsou v řetězci jen číslice,\nmohlo by to fungovat nějak takhle:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"k\">def</span> <span class=\"nf\">nacti_cislo</span><span class=\"p\">():</span>\n <span class=\"k\">while</span> <span class=\"bp\">True</span><span class=\"p\">:</span>\n <span class=\"n\">odpoved</span> <span class=\"o\">=</span> <span class=\"nb\">input</span><span class=\"p\">(</span><span class=\"s1\">'Zadej číslo: '</span><span class=\"p\">)</span>\n <span class=\"k\">if</span> <span class=\"n\">obsahuje_jen_cislice</span><span class=\"p\">(</span><span class=\"n\">odpoved</span><span class=\"p\">):</span>\n <span class=\"k\">return</span> <span class=\"nb\">int</span><span class=\"p\">(</span><span class=\"n\">odpoved</span><span class=\"p\">)</span> <span class=\"c1\"># máme výsledek, funkce končí</span>\n <span class=\"k\">else</span><span class=\"p\">:</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s1\">'To nebylo číslo!'</span><span class=\"p\">)</span>\n <span class=\"c1\"># ... a zeptáme se znovu -- cyklus `while` pokračuje</span>\n</pre></div><p>Kde ale vzít funkci <code>obsahuje_jen_cislice</code>?\nNechceme ji psát znovu – funkce <code>int</code> sama nejlíp pozná, co se dá převést na\nčíslo a co ne.\nA dokonce nám to dá vědět – chybou, kterou můžeme <em>zachytit</em>.</p>\n<h2>Ošetření chyby</h2>\n<p>Pro zachycení chyby má Python příkaz <code>try</code>/<code>except</code>.</p>\n<div class=\"highlight\"><pre><span></span><span class=\"k\">def</span> <span class=\"nf\">nacti_cislo</span><span class=\"p\">():</span>\n <span class=\"k\">while</span> <span class=\"bp\">True</span><span class=\"p\">:</span>\n <span class=\"n\">odpoved</span> <span class=\"o\">=</span> <span class=\"nb\">input</span><span class=\"p\">(</span><span class=\"s1\">'Zadej číslo: '</span><span class=\"p\">)</span>\n <span class=\"k\">try</span><span class=\"p\">:</span>\n <span class=\"k\">return</span> <span class=\"nb\">int</span><span class=\"p\">(</span><span class=\"n\">odpoved</span><span class=\"p\">)</span>\n <span class=\"k\">except</span> <span class=\"ne\">ValueError</span><span class=\"p\">:</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s1\">'To nebylo číslo!'</span><span class=\"p\">)</span>\n</pre></div><p>Jak to funguje?\nPříkazy v bloku uvozeném příkazem <code>try</code> se normálně provádějí, ale když\nnastane uvedená výjimka, Python přeskočí zbytek bloky <code>try</code> a provede všechno \nv bloku <code>except</code>.\nPokud výjimka nenastala, přeskočí se celý blok <code>except</code>.</p>\n<h2>Druhy chyb</h2>\n<p>A co je to <code>ValueError</code>? To je typ chyby.\nPodobných typů je spousta.\nVšechny jsou popsané <a href=\"https://docs.python.org/3/library/exceptions.html#exception-hierarchy\">v dokumentaci</a>; pro nás jsou (nebo budou) důležité tyto:</p>\n<div class=\"highlight\"><pre><code>BaseException\n ├── SystemExit vyvolána funkcí exit()\n ├── KeyboardInterrupt vyvolána po stisknutí Ctrl+C\n ╰── Exception\n ├── ArithmeticError\n │ ╰── ZeroDivisionError dělení nulou\n ├── AssertionError nepovedený příkaz `assert`\n ├── AttributeError neexistující atribut/metoda, např. 'abc'.len\n ├── ImportError nepovedený import\n ├── LookupError\n │ ╰── IndexError neexistující index, např. 'abc'[999]\n ├── NameError použití neexistujícího jména proměnné\n │ ╰── UnboundLocalError použití proměnné, která ještě nebyla nastavená\n ├── SyntaxError špatná syntaxe, program je nečitelný/nepoužitelný\n │ ╰── IndentationError špatné odsazení\n │ ╰── TabError kombinování mezer a tabulátorů v odsazení\n ├── TypeError špatný typ, např. len(9)\n ╰── ValueError špatná hodnota, např. int('xyz')</code></pre></div><p>Když odchytáváš obecnou výjimku,\nchytnou se i všechny podřízené typy výjimek –\nnapříklad <code>except ArithmeticError:</code> zachytí i <code>ZeroDivisionError</code>.\nA <code>except Exception:</code> zachytí <em>všechny</em> výjimky, které běžně chceš zachytit.</p>\n<h2>Nechytej je všechny!</h2>\n<p>Většinu chyb <em>není</em> potřeba ošetřovat.</p>\n<p>Nastane-li <em>nečekaná</em> situace, je téměř vždy\nmnohem lepší program ukončit, než se snažit\npokračovat dál počítat se špatnými hodnotami.\nNavíc chybový výstup, který Python standardně\npřipraví, může hodně ulehčit hledání chyby.</p>\n<p>Zachytávej tedy jenom ty chyby, které <em>očekáváš</em> – víš přesně, která chyba může\nnastat a proč, a máš možnost správně zareagovat.</p>\n<p>V našem příkladu to platí pro <code>ValueError</code> z funkce <code>int</code>.</p>\n<p>Co ale dělat, kdyš uživatel chce ukončit program a zmáčkne\n<kbd>Ctrl</kbd>+<kbd>C</kbd>?\nNebo když se mu porouchá klávesnice a selže funkce <code>input</code>?\nV obou případech je nejlepší reakce ukončit program a informovat\nprogramátora, že (a kde) je něco špatně.\nNeboli vypat chybovou hlášku – to, co se stane normálně, bez <code>try</code>.</p>\n<h2>Další přílohy k <code>try</code></h2>\n<p>Pro úplnost: kromě <code>except</code> existují dva jiné bloky,\nkteré můžeš „přilepit“ k <code>try</code>, a to <code>else</code> a <code>finally</code>.\nPrvní se provede, když v <code>try</code> bloku\nžádná chyba nenastane; druhý se provede vždy – ať\nuž chyba nastala nebo ne.</p>\n<p>Můžeš taky použít více bloků <code>except</code>. Provede se vždy maximálně jeden:\nten první, který danou chybu umí ošetřit.</p>\n<div class=\"highlight\"><pre><span></span><span class=\"k\">try</span><span class=\"p\">:</span>\n <span class=\"n\">neco_udelej</span><span class=\"p\">()</span>\n<span class=\"k\">except</span> <span class=\"ne\">ValueError</span><span class=\"p\">:</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s1\">'Tohle se provede, pokud nastane ValueError'</span><span class=\"p\">)</span>\n<span class=\"k\">except</span> <span class=\"ne\">NameError</span><span class=\"p\">:</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s1\">'Tohle se provede, pokud nastane NameError'</span><span class=\"p\">)</span>\n<span class=\"k\">except</span> <span class=\"ne\">Exception</span><span class=\"p\">:</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s1\">'Tohle se provede, pokud nastane jiná chyba'</span><span class=\"p\">)</span>\n <span class=\"c1\"># (kromě SystemExit a KeyboardInterrupt, ty chytat nechceme)</span>\n<span class=\"k\">except</span> <span class=\"ne\">TypeError</span><span class=\"p\">:</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s1\">'Tohle se neprovede nikdy'</span><span class=\"p\">)</span>\n <span class=\"c1\"># ("except Exception" výše ošetřuje i TypeError; sem se Python nedostane)</span>\n<span class=\"k\">else</span><span class=\"p\">:</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s1\">'Tohle se provede, pokud chyba nenastane'</span><span class=\"p\">)</span>\n<span class=\"k\">finally</span><span class=\"p\">:</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s1\">'Tohle se provede vždycky; i pokud v `try` bloku byl např. `return`'</span><span class=\"p\">)</span>\n</pre></div><h2>Vyvolání chyby</h2>\n<p>Občas se stane, že výjimku budeš potřebovat vyvolat sama.</p>\n<p>Často se to stává když píšeš nějakou obecnou funkci.\nTřeba funkci na výpočet obsahu čtverce.\nCo se stane, když někdo zavolá <code>obsah_ctverce(-5)</code>?</p>\n<ul>\n<li>Zadal-li ono <code>-5</code> uživatel, je potřeba mu vynadat a zeptat se znovu.</li>\n<li>Naměřil-li <code>-5</code> nějaký robotický aparát, je potřeba ho líp zkalibrovat.</li>\n<li>Vyšel-li čtverec se stranou <code>-5</code> v nějakém výpočtu, je nejspíš potřeba opravit\nchybu v tom výpočtu.</li>\n</ul>\n<p>Samotná funkce <code>obsah_ctverce</code> ale „neví“, proč ji někdo volá.\nJejím úkolem je jen něco spočítat.\nMěla by být použitelná ve všech případech výše – a v mnoha dalších.</p>\n<p>Když někdo zavolá <code>obsah_ctverce(-5)</code>, <em>neexistuje</em> správný výsledek, který by\nfunkce mohla vrátit.\nMísto vracení výsledku musí tato funkce <em>signalizovat chybu</em>.\nS tou se pak může program, který <code>obsah_ctverce(-5)</code> zavolal,\nvypořádat – vynadat uživateli, zkalibrovat měřák, nebo, pokud na chybu není\npřipravený, sám skončit s chybou (a upozornit tak programátora, že je něco\nšpatně).</p>\n<p>Jak na to prakticky?\nChybu můžeš vyvolat pomocí příkazu <code>raise</code>.\nZa příkaz dáš druh výjimky a pak do závorek nějaký popis toho, co je špatně.</p>\n<div class=\"highlight\"><pre><span></span><span class=\"k\">def</span> <span class=\"nf\">obsah_ctverce</span><span class=\"p\">(</span><span class=\"n\">strana</span><span class=\"p\">):</span>\n <span class=\"k\">if</span> <span class=\"n\">strana</span> <span class=\"o\">></span> <span class=\"mi\">0</span><span class=\"p\">:</span>\n <span class=\"k\">return</span> <span class=\"n\">strana</span> <span class=\"o\">*</span> <span class=\"n\">strana</span>\n <span class=\"k\">else</span><span class=\"p\">:</span>\n <span class=\"k\">raise</span> <span class=\"ne\">ValueError</span><span class=\"p\">(</span><span class=\"n\">f</span><span class=\"s1\">'Strana musí být kladná, číslo {strana} kladné není!'</span><span class=\"p\">)</span>\n</pre></div><p>Podobně jako <code>return</code>, i příkaz <code>raise</code> ukončí funkci.\nA nejen tu – pokud na tuhle konkrétní chybu program předem připravený,\nukončí se celý program.</p>\n<p>Ze začátku není příliš důležité dumat nad tím, který typ výjimky je ten\nsprávný.\nKlidně „střílej od boku“.\n<code>ValueError</code> bývá často správná volba.</p>\n\n\n " } } }