Skip to content

Commit 71bad9e

Browse files
committed
Rework text on exceptions; split out materials on nested tracebacks
1 parent 6c133ed commit 71bad9e

4 files changed

Lines changed: 186 additions & 139 deletions

File tree

courses/pyladies/info.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ plan:
5959
slug: def-str
6060
materials:
6161
- lesson: beginners/def
62+
- lesson: beginners/nested-traceback
6263
- lesson: beginners/str
6364
- lesson: beginners/fstring
6465
- title: Řetězcový tahák
Lines changed: 110 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,81 @@
11
# Výjimky
22

3-
O [chybových výpisech]({{ lesson_url('beginners/print') }}) už v tomto
4-
kurzu byla zmínka: Python si postěžuje, řekne, kde je chyba, a ukončí program.
5-
O chybách se toho ale dá říct mnohem víc.
3+
Pojďme si prohloubit znalosti o chybách, neboli odborně o *výjimkách*
4+
(angl. *exceptions*).
65

7-
8-
## Výpisy chyb
9-
10-
Na začátku si ukážeme (nebo zopakujeme), jak Python vypíše chybu, která
11-
nastane v zanořené funkci:
6+
Vezměme následující funkci:
127

138
```python
14-
def vnejsi_funkce():
15-
return vnitrni_funkce(0)
16-
17-
def vnitrni_funkce(delitel):
18-
return 1 / delitel
19-
20-
print(vnejsi_funkce())
9+
def nacti_cislo():
10+
odpoved = input('Zadej číslo: ')
11+
return int(odpoved)
2112
```
2213

23-
<!-- XXX: Highlight the line numbers -->
14+
Když uživatel nezadá číslice, ale třeba text `cokolada`,
15+
nastene výjimka jménem `ValueError` (chyba hodnoty) a Python vypíše
16+
odpovídající chybovou hlášku.
2417

2518
```pycon
26-
Traceback (most recent call last):
27-
File "/tmp/ukazka.py", line 7, in <module>
28-
print(vnejsi_funkce())
29-
File "/tmp/ukazka.py", line 2, in vnejsi_funkce
30-
return vnitrni_funkce(0)
31-
File "/tmp/ukazka.py", line 5, in vnitrni_funkce
32-
return 1 / delitel
33-
ZeroDivisionError: division by zero
19+
Traceback (most recent call last):
20+
File "ukazka.py", line 3, in nacti_cislo
21+
cislo = int(odpoved)
22+
ValueError: invalid literal for int() with base 10: 'cokolada'
3423
```
3524

36-
Všimni si, že každá funkce, jejíž volání vedlo k chybě, je uvedena ve výpisu.
37-
Skutečná chyba (tedy místo, které musíme opravit)
38-
je pravděpodobně poblíž některého z těchto volání.
39-
V našem případě bychom asi neměl{{gnd('i', 'y', both='i')}} volat
40-
`vnitrni_funkce` s argumentem `0`.
41-
A nebo by `vnitrni_funkce` měla být na nulu
42-
připravená a dělat v tomto případě něco jiného.
25+
Program volá funkci `int()` pro neco, co nedává smysl jako číslo.
26+
Co s tím má chudák funkce `int` dělat?
27+
Není žádná rozumná hodnota, kterou by mohla vrátit.
28+
Převádění tohoto textu na celé číslo nedává smysl.
4329

44-
Python nemůže vědět, na kterém místě by se chyba měla opravit, a tak ukáže vše.
45-
Ve složitějších programech se to bude hodit.
30+
Až funkce `nacti_cislo` nejlíp „ví“, co se má stát, když uživatel nezadá
31+
číslice.
32+
Stačí se uživatele zeptat znovu!
33+
Kdybychom měli funkci, která zjistí jestli jsou v řetězci jen číslice,
34+
mohlo by to fungovat nějak takhle:
4635

36+
```python
37+
def nacti_cislo():
38+
while True:
39+
odpoved = input('Zadej číslo: ')
40+
if obsahuje_jen_cislice(odpoved):
41+
return int(odpoved) # máme výsledek, funkce končí
42+
else:
43+
print('To nebylo číslo!')
44+
# ... a zeptáme se znovu -- cyklus `while` pokračuje
45+
```
4746

48-
## Vyvolání chyby
47+
Kde ale vzít funkci `obsahuje_jen_cislice`?
48+
Nechceme ji psát znovu – funkce `int` sama nejlíp pozná, co se dá převést na
49+
číslo a co ne.
50+
A dokonce nám to dá vědět – chybou, kterou můžeme *zachytit*.
4951

50-
Chybu neboli *výjimku* (angl. *exception*) můžeš vyvolat i {{gnd('sám', 'sama')}},
51-
pomocí příkazu `raise`.
52-
Za příkaz dáš jméno výjimky a pak do závorek nějaký popis toho, co je špatně.
5352

54-
```python
55-
VELIKOST_POLE = 20
53+
## Ošetření chyby
5654

57-
def over_cislo(cislo):
58-
if 0 <= cislo < VELIKOST_POLE:
59-
print('OK!')
60-
else:
61-
raise ValueError('Čislo {n} není v poli!'.format(n=cislo))
55+
Pro zachycení chyby má Python příkaz `try`/`except`.
56+
57+
```python
58+
def nacti_cislo():
59+
while True:
60+
odpoved = input('Zadej číslo: ')
61+
try:
62+
return int(odpoved)
63+
except ValueError:
64+
print('To nebylo číslo!')
6265
```
6366

64-
Všechny typy výjimek, které jsou zabudované
65-
v Pythonu, jsou popsané [v dokumentaci](https://docs.python.org/3.2/library/exceptions.html#exception-hierarchy).
67+
Jak to funguje?
68+
Příkazy v bloku uvozeném příkazem `try` se normálně provádějí, ale když
69+
nastane uvedená výjimka, Python přeskočí zbytek bloky `try` a provede všechno
70+
v bloku `except`.
71+
Pokud výjimka nenastala, přeskočí se celý blok `except`.
72+
73+
74+
## Druhy chyb
6675

67-
Pro nás jsou (nebo budou) důležité tyto:
76+
A co je to `ValueError`? To je typ chyby.
77+
Podobných typů je spousta.
78+
Všechny jsou popsané [v dokumentaci](https://docs.python.org/3/library/exceptions.html#exception-hierarchy); pro nás jsou (nebo budou) důležité tyto:
6879

6980
```plain
7081
BaseException
@@ -74,86 +85,51 @@ BaseException
7485
├── ArithmeticError
7586
│ ╰── ZeroDivisionError dělení nulou
7687
├── AssertionError nepovedený příkaz `assert`
77-
├── AttributeError neexistující atribut, např. 'abc'.len
88+
├── AttributeError neexistující atribut/metoda, např. 'abc'.len
7889
├── ImportError nepovedený import
7990
├── LookupError
8091
│ ╰── IndexError neexistující index, např. 'abc'[999]
8192
├── NameError použití neexistujícího jména proměnné
8293
│ ╰── UnboundLocalError použití proměnné, která ještě nebyla nastavená
83-
├── SyntaxError špatná syntaxe program je nečitelný/nepoužitelný
94+
├── SyntaxError špatná syntaxe, program je nečitelný/nepoužitelný
8495
│ ╰── IndentationError špatné odsazení
85-
│ ╰── TabError kombinování mezer a tabulátorů
96+
│ ╰── TabError kombinování mezer a tabulátorů v odsazení
8697
├── TypeError špatný typ, např. len(9)
8798
╰── ValueError špatná hodnota, např. int('xyz')
8899
```
89100

90-
91-
## Ošetření chyby
92-
93-
A proč jich je tolik druhů?
94-
Abys je mohl{{a}} chytat!
95-
Následující funkce je připravená na to, že
96-
funkce `int` může selhat, pokud uživatel nezadá číslo:
97-
98-
```python
99-
def nacti_cislo():
100-
odpoved = input('Zadej číslo: ')
101-
try:
102-
cislo = int(odpoved)
103-
except ValueError:
104-
print('To nebylo číslo! Pokračuji s nulou.')
105-
cislo = 0
106-
return cislo
107-
```
108-
109-
Jak to funguje?
110-
Příkazy v bloku uvozeném příkazem `try` se normálně provádějí, ale když
111-
nastane uvedená výjimka, Python místo ukončení programu provede
112-
všechno v bloku `except`.
113-
Když výjimka nenastane, blok `except` se přeskočí.
114-
115101
Když odchytáváš obecnou výjimku,
116102
chytnou se i všechny podřízené typy výjimek –
117103
například `except ArithmeticError:` zachytí i `ZeroDivisionError`.
118-
A `except Exception:` zachytí všechny
119-
výjimky, které běžně chceš zachytit.
104+
A `except Exception:` zachytí *všechny* výjimky, které běžně chceš zachytit.
120105

121106

122107
## Nechytej je všechny!
123108

124-
Většinu chyb ale není potřeba ošetřovat.
109+
Většinu chyb *není* potřeba ošetřovat.
125110

126-
Nastane-li nečekaná situace, je téměř vždy
127-
*mnohem* lepší program ukončit, než se snažit
111+
Nastane-li *nečekaná* situace, je téměř vždy
112+
mnohem lepší program ukončit, než se snažit
128113
pokračovat dál počítat se špatnými hodnotami.
129114
Navíc chybový výstup, který Python standardně
130115
připraví, může hodně ulehčit hledání chyby.
131116

132-
„Ošetřování” chyb jako `KeyboardInterrupt`
133-
je ještě horší: může způsobit, že program nepůjde
134-
ukončit, když bude potřeba.
117+
Zachytávej tedy jenom ty chyby, které *očekáváš* – víš přesně, která chyba může
118+
nastat a proč, a máš možnost správně zareagovat.
135119

136-
Příkaz `try/except` proto používej
137-
jen v situacích, kdy výjimku očekáváš – víš přesně, která chyba může
138-
nastat a proč, a máš možnost ji opravit.
139-
Pro nás to typicky bude načítání vstupu od uživatele.
140-
Po špatném pokusu o zadání je dobré se ptát znovu, dokud uživatel nezadá
141-
něco smysluplného:
120+
V našem příkladu to platí pro `ValueError` z funkce `int`.
142121

143-
```python
144-
def nacti_cislo():
145-
while True:
146-
odpoved = input('Zadej číslo: ')
147-
try:
148-
return int(odpoved)
149-
except ValueError:
150-
print('To nebylo číslo! Zkus to znovu.')
151-
```
122+
Co ale dělat, kdyš uživatel chce ukončit program a zmáčkne
123+
<kbd>Ctrl</kbd>+<kbd>C</kbd>?
124+
Nebo když se mu porouchá klávesnice a selže funkce `input`?
125+
V obou případech je nejlepší reakce ukončit program a informovat
126+
programátora, že (a kde) je něco špatně.
127+
Neboli vypat chybovou hlášku – to, co se stane normálně, bez `try`.
152128

153129

154130
## Další přílohy k `try`
155131

156-
Kromě `except` existují dva jiné bloky,
132+
Pro úplnost: kromě `except` existují dva jiné bloky,
157133
které můžeš „přilepit“ k `try`, a to `else` a `finally`.
158134
První se provede, když v `try` bloku
159135
žádná chyba nenastane; druhý se provede vždy – ať
@@ -182,54 +158,49 @@ finally:
182158
```
183159

184160

185-
## Úkol
186-
187-
Doplň do geometrické kalkulačky (nebo 1-D piškvorek, máš-li je) ošetření chyby,
188-
která nastane když uživatel nezadá číslo.
189161

190-
{% filter solution %}
162+
## Vyvolání chyby
191163

192-
Možné řešení pro geometrickou kalkulačku:
164+
Občas se stane, že výjimku budeš potřebovat vyvolat {{gnd('sám', 'sama')}}.
193165

194-
```python
166+
Často se to stává když píšeš nějakou obecnou funkci.
167+
Třeba funkci na výpočet obsahu čtverce.
168+
Co se stane, když někdo zavolá `obsah_ctverce(-5)`?
195169

196-
while True:
197-
try:
198-
strana = float(input('Zadej stranu čtverce v centimetrech: '))
199-
except ValueError:
200-
print('To nebylo číslo!')
201-
else:
202-
if strana <= 0:
203-
print('To nedává smysl!')
204-
else:
205-
break
170+
* Zadal-li ono `-5` uživatel, je potřeba mu vynadat a zeptat se znovu.
171+
* Naměřil-li `-5` nějaký robotický aparát, je potřeba ho líp zkalibrovat.
172+
* Vyšel-li čtverec se stranou `-5` v nějakém výpočtu, je nejspíš potřeba opravit
173+
chybu v tom výpočtu.
206174

207-
print('Obvod čtverce se stranou', strana, 'je', 4 * strana, 'cm')
208-
print('Obsah čtverce se stranou', strana, 'je', strana * strana, 'cm2')
175+
Samotná funkce `obsah_ctverce` ale „neví“, proč ji někdo volá.
176+
Jejím úkolem je jen něco spočítat.
177+
Měla by být použitelná ve všech případech výše – a v mnoha dalších.
209178

210-
```
179+
Když někdo zavolá `obsah_ctverce(-5)`, *neexistuje* správný výsledek, který by
180+
funkce mohla vrátit.
181+
Místo vracení výsledku musí tato funkce *signalizovat chybu*.
182+
S tou se pak může program, který `obsah_ctverce(-5)` zavolal,
183+
vypořádat – vynadat uživateli, zkalibrovat měřák, nebo, pokud na chybu není
184+
připravený, sám skončit s chybou (a upozornit tak programátora, že je něco
185+
špatně).
211186

212-
Možné řešení pro 1-D piškvorky:
187+
Jak na to prakticky?
188+
Chybu můžeš vyvolat pomocí příkazu `raise`.
189+
Za příkaz dáš druh výjimky a pak do závorek nějaký popis toho, co je špatně.
213190

214191
```python
215-
def tah_hrace(pole):
216-
while True:
217-
try:
218-
pozice = int(input('Kam chceš hrát? (0..19) '))
219-
except ValueError:
220-
print('To není číslo!')
221-
else:
222-
if pozice < 0 or pozice >= len(pole):
223-
print('Nemůžeš hrát venku z pole!')
224-
elif pole[pozice] != '-':
225-
print('Tam není volno!')
226-
else:
227-
break
228-
229-
pole = pole[:pozice] + 'o' + pole[pozice + 1:]
230-
return pole
192+
def obsah_ctverce(strana):
193+
if strana > 0:
194+
return strana * strana
195+
else:
196+
raise ValueError(f'Strana musí být kladná, číslo {strana} kladné není!')
197+
```
231198

199+
Podobně jako `return`, i příkaz `raise` ukončí funkci.
200+
A nejen tu – pokud na tuhle konkrétní chybu program předem připravený,
201+
ukončí se celý program.
232202

233-
print(tah_hrace('-x----'))
234-
```
235-
{% endfilter %}
203+
Ze začátku není příliš důležité dumat nad tím, který typ výjimky je ten
204+
správný.
205+
Klidně „střílej od boku“.
206+
`ValueError` bývá často správná volba.

0 commit comments

Comments
 (0)