O chybových výpisech už v tomto kurzu byla zmínka: Python si postěžuje, řekne, kde je chyba, a ukončí program. O chybách se toho ale dá říct mnohem víc.
Na začátku si ukážeme (nebo zopakujeme), jak Python vypíše chybu, která nastane v zanořené funkci:
def vnejsi_funkce():
return vnitrni_funkce(0)
def vnitrni_funkce(delitel):
return 1 / delitel
print(vnejsi_funkce())
Traceback (most recent call last):
File "/tmp/ukazka.py", line 7, in <module>
print(vnejsi_funkce())
File "/tmp/ukazka.py", line 2, in vnejsi_funkce
return vnitrni_funkce(0)
File "/tmp/ukazka.py", line 5, in vnitrni_funkce
return 1 / delitel
ZeroDivisionError: division by zero
Všimni si, že každá funkce, jejíž volání vedlo k chybě, je uvedena ve výpisu.
Skutečná chyba (tedy místo, které musíme opravit)
je pravděpodobně poblíž některého z těchto volání.
V našem případě bychom asi neměli volat
vnitrni_funkce
s argumentem 0
.
A nebo by vnitrni_funkce
měla být na nulu
připravená a dělat v tomto případě něco jiného.
Python nemůže vědět, na kterém místě by se chyba měla opravit, a tak ukáže vše. Ve složitějších programech se to bude hodit.
Chybu neboli výjimku (angl. exception) můžeš vyvolat i sám/sama,
pomocí příkazu raise
.
Za příkaz dáš jméno výjimky a pak do závorek nějaký popis toho, co je špatně.
VELIKOST_POLE = 20
def over_cislo(cislo):
if 0 <= cislo < VELIKOST_POLE:
print('OK!')
else:
raise ValueError('Čislo {n} není v poli!'.format(n=cislo))
Všechny typy výjimek, které jsou zabudované v Pythonu, 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, 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ů
├── TypeError špatný typ, např. len(9)
╰── ValueError špatná hodnota, např. int('xyz')
A proč jich je tolik druhů?
Abys je mohl/a chytat!
Následující funkce je připravená na to, že
funkce int
může selhat, pokud uživatel nezadá číslo:
def nacti_cislo():
odpoved = input('Zadej číslo: ')
try:
cislo = int(odpoved)
except ValueError:
print('To nebylo číslo! Pokračuji s nulou.')
cislo = 0
return cislo
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 místo ukončení programu provede
všechno v bloku except
.
Když výjimka nenastane, blok except
se přeskočí.
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 ale 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.
„Ošetřování” chyb jako KeyboardInterrupt
je ještě horší: může způsobit, že program nepůjde
ukončit, když bude potřeba.
Příkaz try/except
proto používej
jen v situacích, kdy výjimku očekáváš – víš přesně, která chyba může
nastat a proč, a máš možnost ji opravit.
Pro nás to typicky bude načítání vstupu od uživatele.
Po špatném pokusu o zadání je dobré se ptát znovu, dokud uživatel nezadá
něco smysluplného:
def nacti_cislo():
while True:
odpoved = input('Zadej číslo: ')
try:
return int(odpoved)
except ValueError:
print('To nebylo číslo! Zkus to znovu.')
try
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`')
Doplň do geometrické kalkulačky (nebo 1-D piškvorek, máš-li je) ošetření chyby, která nastane když uživatel nezadá číslo.
{ "data": { "sessionMaterial": { "id": "session-material:2019/pyladies-hradec-jaro:modules:0", "title": "Výjimky", "html": "\n \n \n\n <h1>Výjimky</h1>\n<p>O <a href=\"/2019/pyladies-hradec-jaro/beginners/print/\">chybových výpisech</a> už v tomto\nkurzu byla zmínka: Python si postěžuje, řekne, kde je chyba, a ukončí program.\nO chybách se toho ale dá říct mnohem víc.</p>\n<h2>Výpisy chyb</h2>\n<p>Na začátku si ukážeme (nebo zopakujeme), jak Python vypíše chybu, která\nnastane v zanořené funkci:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"k\">def</span> <span class=\"nf\">vnejsi_funkce</span><span class=\"p\">():</span>\n <span class=\"k\">return</span> <span class=\"n\">vnitrni_funkce</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">)</span>\n\n<span class=\"k\">def</span> <span class=\"nf\">vnitrni_funkce</span><span class=\"p\">(</span><span class=\"n\">delitel</span><span class=\"p\">):</span>\n <span class=\"k\">return</span> <span class=\"mi\">1</span> <span class=\"o\">/</span> <span class=\"n\">delitel</span>\n\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">vnejsi_funkce</span><span class=\"p\">())</span>\n</pre></div><!-- XXX: Highlight the line numbers -->\n\n<div class=\"highlight\"><pre><span></span><span class=\"gt\">Traceback (most recent call last): </span>\n File <span class=\"nb\">"/tmp/ukazka.py"</span>, line <span class=\"m\">7</span>, in <span class=\"n\"><module></span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">vnejsi_funkce</span><span class=\"p\">())</span>\n File <span class=\"nb\">"/tmp/ukazka.py"</span>, line <span class=\"m\">2</span>, in <span class=\"n\">vnejsi_funkce</span>\n <span class=\"k\">return</span> <span class=\"n\">vnitrni_funkce</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">)</span>\n File <span class=\"nb\">"/tmp/ukazka.py"</span>, line <span class=\"m\">5</span>, in <span class=\"n\">vnitrni_funkce</span>\n <span class=\"k\">return</span> <span class=\"mi\">1</span> <span class=\"o\">/</span> <span class=\"n\">delitel</span>\n<span class=\"gr\">ZeroDivisionError</span>: <span class=\"n\">division by zero</span>\n</pre></div><p>Všimni si, že každá funkce, jejíž volání vedlo k chybě, je uvedena ve výpisu.\nSkutečná chyba (tedy místo, které musíme opravit)\nje pravděpodobně poblíž některého z těchto volání.\nV našem případě bychom asi neměli volat\n<code>vnitrni_funkce</code> s argumentem <code>0</code>.\nA nebo by <code>vnitrni_funkce</code> měla být na nulu\npřipravená a dělat v tomto případě něco jiného.</p>\n<p>Python nemůže vědět, na kterém místě by se chyba měla opravit, a tak ukáže vše.\nVe složitějších programech se to bude hodit.</p>\n<h2>Vyvolání chyby</h2>\n<p>Chybu neboli <em>výjimku</em> (angl. <em>exception</em>) můžeš vyvolat i sám/sama,\npomocí příkazu <code>raise</code>.\nZa příkaz dáš jméno výjimky a pak do závorek nějaký popis toho, co je špatně.</p>\n<div class=\"highlight\"><pre><span></span><span class=\"n\">VELIKOST_POLE</span> <span class=\"o\">=</span> <span class=\"mi\">20</span>\n\n<span class=\"k\">def</span> <span class=\"nf\">over_cislo</span><span class=\"p\">(</span><span class=\"n\">cislo</span><span class=\"p\">):</span>\n <span class=\"k\">if</span> <span class=\"mi\">0</span> <span class=\"o\"><=</span> <span class=\"n\">cislo</span> <span class=\"o\"><</span> <span class=\"n\">VELIKOST_POLE</span><span class=\"p\">:</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s1\">'OK!'</span><span class=\"p\">)</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=\"s1\">'Čislo {n} není v poli!'</span><span class=\"o\">.</span><span class=\"n\">format</span><span class=\"p\">(</span><span class=\"n\">n</span><span class=\"o\">=</span><span class=\"n\">cislo</span><span class=\"p\">))</span>\n</pre></div><p>Všechny typy výjimek, které jsou zabudované\nv Pythonu, jsou popsané <a href=\"https://docs.python.org/3.2/library/exceptions.html#exception-hierarchy\">v dokumentaci</a>.</p>\n<p>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, 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ů\n ├── TypeError špatný typ, např. len(9)\n ╰── ValueError špatná hodnota, např. int('xyz')</code></pre></div><h2>Ošetření chyby</h2>\n<p>A proč jich je tolik druhů?\nAbys je mohl/a chytat!\nNásledující funkce je připravená na to, že\nfunkce <code>int</code> může selhat, pokud uživatel nezadá číslo:</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\">try</span><span class=\"p\">:</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=\"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! Pokračuji s nulou.'</span><span class=\"p\">)</span>\n <span class=\"n\">cislo</span> <span class=\"o\">=</span> <span class=\"mi\">0</span>\n <span class=\"k\">return</span> <span class=\"n\">cislo</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 místo ukončení programu provede\nvšechno v bloku <code>except</code>.\nKdyž výjimka nenastane, blok <code>except</code> se přeskočí.</p>\n<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í všechny\nvýjimky, které běžně chceš zachytit.</p>\n<h2>Nechytej je všechny!</h2>\n<p>Většinu chyb ale není potřeba ošetřovat.</p>\n<p>Nastane-li nečekaná situace, je téměř vždy\n<em>mnohem</em> 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>„Ošetřování” chyb jako <code>KeyboardInterrupt</code>\nje ještě horší: může způsobit, že program nepůjde\nukončit, když bude potřeba.</p>\n<p>Příkaz <code>try/except</code> proto používej\njen v situacích, kdy výjimku očekáváš – víš přesně, která chyba může\nnastat a proč, a máš možnost ji opravit.\nPro nás to typicky bude načítání vstupu od uživatele.\nPo špatném pokusu o zadání je dobré se ptát znovu, dokud uživatel nezadá\nněco smysluplného:</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! Zkus to znovu.'</span><span class=\"p\">)</span>\n</pre></div><h2>Další přílohy k <code>try</code></h2>\n<p>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>Úkol</h2>\n<p>Doplň do geometrické kalkulačky (nebo 1-D piškvorek, máš-li je) ošetření chyby,\nkterá nastane když uživatel nezadá číslo.</p>\n<div class=\"solution\" id=\"solution-0\">\n <h3>Řešení</h3>\n <div class=\"solution-cover\">\n <a href=\"/2019/pyladies-hradec-jaro/beginners/exceptions/index/solutions/0/\"><span class=\"link-text\">Ukázat řešení</span></a>\n </div>\n <div class=\"solution-body\" aria-hidden=\"true\">\n <p>Možné řešení pro geometrickou kalkulačku:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"k\">while</span> <span class=\"bp\">True</span><span class=\"p\">:</span>\n <span class=\"k\">try</span><span class=\"p\">:</span>\n <span class=\"n\">strana</span> <span class=\"o\">=</span> <span class=\"nb\">float</span><span class=\"p\">(</span><span class=\"nb\">input</span><span class=\"p\">(</span><span class=\"s1\">'Zadej stranu čtverce v centimetrech: '</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 <span class=\"k\">else</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\">print</span><span class=\"p\">(</span><span class=\"s1\">'To nedává smysl!'</span><span class=\"p\">)</span>\n <span class=\"k\">else</span><span class=\"p\">:</span>\n <span class=\"k\">break</span>\n\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s1\">'Obvod čtverce se stranou'</span><span class=\"p\">,</span> <span class=\"n\">strana</span><span class=\"p\">,</span> <span class=\"s1\">'je'</span><span class=\"p\">,</span> <span class=\"mi\">4</span> <span class=\"o\">*</span> <span class=\"n\">strana</span><span class=\"p\">,</span> <span class=\"s1\">'cm'</span><span class=\"p\">)</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s1\">'Obsah čtverce se stranou'</span><span class=\"p\">,</span> <span class=\"n\">strana</span><span class=\"p\">,</span> <span class=\"s1\">'je'</span><span class=\"p\">,</span> <span class=\"n\">strana</span> <span class=\"o\">*</span> <span class=\"n\">strana</span><span class=\"p\">,</span> <span class=\"s1\">'cm2'</span><span class=\"p\">)</span>\n</pre></div><p>Možné řešení pro 1-D piškvorky:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"k\">def</span> <span class=\"nf\">tah_hrace</span><span class=\"p\">(</span><span class=\"n\">pole</span><span class=\"p\">):</span>\n <span class=\"k\">while</span> <span class=\"bp\">True</span><span class=\"p\">:</span>\n <span class=\"k\">try</span><span class=\"p\">:</span>\n <span class=\"n\">pozice</span> <span class=\"o\">=</span> <span class=\"nb\">int</span><span class=\"p\">(</span><span class=\"nb\">input</span><span class=\"p\">(</span><span class=\"s1\">'Kam chceš hrát? (0..19) '</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 není číslo!'</span><span class=\"p\">)</span>\n <span class=\"k\">else</span><span class=\"p\">:</span>\n <span class=\"k\">if</span> <span class=\"n\">pozice</span> <span class=\"o\"><</span> <span class=\"mi\">0</span> <span class=\"ow\">or</span> <span class=\"n\">pozice</span> <span class=\"o\">>=</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">pole</span><span class=\"p\">):</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s1\">'Nemůžeš hrát venku z pole!'</span><span class=\"p\">)</span>\n <span class=\"k\">elif</span> <span class=\"n\">pole</span><span class=\"p\">[</span><span class=\"n\">pozice</span><span class=\"p\">]</span> <span class=\"o\">!=</span> <span class=\"s1\">'-'</span><span class=\"p\">:</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s1\">'Tam není volno!'</span><span class=\"p\">)</span>\n <span class=\"k\">else</span><span class=\"p\">:</span>\n <span class=\"k\">break</span>\n\n <span class=\"n\">pole</span> <span class=\"o\">=</span> <span class=\"n\">pole</span><span class=\"p\">[:</span><span class=\"n\">pozice</span><span class=\"p\">]</span> <span class=\"o\">+</span> <span class=\"s1\">'o'</span> <span class=\"o\">+</span> <span class=\"n\">pole</span><span class=\"p\">[</span><span class=\"n\">pozice</span> <span class=\"o\">+</span> <span class=\"mi\">1</span><span class=\"p\">:]</span>\n <span class=\"k\">return</span> <span class=\"n\">pole</span>\n\n\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">tah_hrace</span><span class=\"p\">(</span><span class=\"s1\">'-x----'</span><span class=\"p\">))</span>\n</pre></div>\n </div>\n</div>\n\n\n " } } }