Skip to content

Commit 29f101a

Browse files
authored
Rework text on exceptions
This is a major update on the text on Exceptions. Also, coverage of *nested tracebacks* is split out and moved next to defining functions. pyvec#630
2 parents f8de71a + 498705d commit 29f101a

4 files changed

Lines changed: 209 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/str-index-slice
6465
- lesson: beginners/str-methods
Lines changed: 133 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,98 @@
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+
Vezmi 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+
nastane 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 něco, 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+
Kdybys měl{{a}} 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+
Nemá smysl 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ůžeš *zachytit*.
51+
52+
> [note]
53+
> Ono „obsahuje_jen_cislice“ v Pythonu existuje. Dokonce několikrát.
54+
> Místo řešení problému to ale spíš ilustruje, v čem problém spočívá:
55+
> * Řetězcová metoda `isnumeric` vrací `True` pokud řetězec obsahuje číslice:
56+
> `'123'.isnumeric()` je pravda; `'abc'.isnumeric()` nepravda.
57+
> Problém je, že funkci `int` potřebuje jeden konkrétní druh číslic:
58+
> pro řetězce jako `'½'` nebo `'௩三๓໓`' (trojka v tamilském, japonském,
59+
> thajském nebo laoském písmu) platí `isnumeric`, ale `int` si na nich
60+
> vyláme zuby stejně jako na `'abc'`.
61+
> * Řetězcová metoda `isdecimal` vrací `True` pokud řetězec obsahuje arabské
62+
> číslice 0-9. To už je lepší, ale stejně to úplně nesedí: `int` si poradí
63+
> s mezerou na začátku, např. s `' 3'`, ale funkce `isdecimal` takový řetězec
64+
> odmítne.
65+
>
66+
> Chceš-li zjistit jestli funkce `int` umí daný řetězec převést na číslo,
67+
> nejlepší je použít přímo funkci `int`.
4968
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ě.
5369

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

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))
72+
Pro zachycení chyby má Python příkaz `try`/`except`.
73+
74+
```python
75+
def nacti_cislo():
76+
while True:
77+
odpoved = input('Zadej číslo: ')
78+
try:
79+
return int(odpoved)
80+
except ValueError:
81+
print('To nebylo číslo!')
6282
```
6383

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).
84+
Jak to funguje?
85+
Příkazy v bloku uvozeném příkazem `try` se normálně provádějí, ale když
86+
nastane uvedená výjimka, Python přeskočí zbytek bloku `try` a provede všechno
87+
v bloku `except`.
88+
Pokud výjimka nenastala, přeskočí se celý blok `except`.
89+
90+
91+
## Druhy chyb
6692

67-
Pro nás jsou (nebo budou) důležité tyto:
93+
A co je to `ValueError`? To je typ chyby.
94+
Podobných typů je spousta.
95+
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:
6896

6997
```plain
7098
BaseException
@@ -74,86 +102,57 @@ BaseException
74102
├── ArithmeticError
75103
│ ╰── ZeroDivisionError dělení nulou
76104
├── AssertionError nepovedený příkaz `assert`
77-
├── AttributeError neexistující atribut, např. 'abc'.len
105+
├── AttributeError neexistující atribut/metoda, např. 'abc'.len
78106
├── ImportError nepovedený import
79107
├── LookupError
80108
│ ╰── IndexError neexistující index, např. 'abc'[999]
81109
├── NameError použití neexistujícího jména proměnné
82110
│ ╰── UnboundLocalError použití proměnné, která ještě nebyla nastavená
83-
├── SyntaxError špatná syntaxe program je nečitelný/nepoužitelný
111+
├── SyntaxError špatná syntaxe, program je nečitelný/nepoužitelný
84112
│ ╰── IndentationError špatné odsazení
85-
│ ╰── TabError kombinování mezer a tabulátorů
113+
│ ╰── TabError kombinování mezer a tabulátorů v odsazení
86114
├── TypeError špatný typ, např. len(9)
87115
╰── ValueError špatná hodnota, např. int('xyz')
88116
```
89117

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čí.
118+
Tohle si není potřeba pamatovat – druh chyby, kterou je potřeba zachytit,
119+
vždy najdeš v příslušné chybové hlášce.
114120

115121
Když odchytáváš obecnou výjimku,
116122
chytnou se i všechny podřízené typy výjimek –
117123
například `except ArithmeticError:` zachytí i `ZeroDivisionError`.
118-
A `except Exception:` zachytí všechny
119-
výjimky, které běžně chceš zachytit.
124+
A `except Exception:` zachytí *všechny* výjimky, které běžně chceš zachytit.
120125

121126

122127
## Nechytej je všechny!
123128

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

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

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.
137+
Zachytávej tedy jenom ty chyby, které *očekáváš* – víš přesně, která chyba může
138+
nastat a proč; máš možnost správně zareagovat.
135139

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:
140+
V našem příkladu to platí pro `ValueError` z funkce `int`: víš že uživatel
141+
nemusí vždy zadat číslo ve správném formátu a víš že správná
142+
reakce na tuhle situaci je problém vysvětlit a zeptat se znovu.
142143

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-
```
144+
Co ale dělat, kdyš uživatel chce ukončit program a zmáčkne
145+
<kbd>Ctrl</kbd>+<kbd>C</kbd>?
146+
Nebo když se mu porouchá klávesnice a selže funkce `input`?
147+
Nejlepší reakce na takovou nečekanou situaci ukončit program a informovat
148+
uživatele (nebo lépe, programátora), že (a kde) je něco špatně.
149+
Neboli vypsat chybovou hlášku.
150+
A to se stane normálně, bez `try`.
152151

153152

154153
## Další přílohy k `try`
155154

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

184183

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.
189184

190-
{% filter solution %}
185+
## Vyvolání chyby
191186

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

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

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
193+
* Zadal-li ono `-5` uživatel, je potřeba mu vynadat a zeptat se znovu.
194+
* Naměřil-li `-5` nějaký robotický aparát, je potřeba ho líp zkalibrovat.
195+
* Vyšel-li čtverec se stranou `-5` v nějakém výpočtu, je nejspíš potřeba opravit
196+
chybu v tom výpočtu.
206197

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

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

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

214214
```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
215+
def obsah_ctverce(strana):
216+
if strana > 0:
217+
return strana ** 2
218+
else:
219+
raise ValueError(f'Strana musí být kladná, číslo {strana} kladné není!')
220+
```
231221

222+
Podobně jako `return`, i příkaz `raise` ukončí funkci.
223+
A nejen tu – pokud na tuhle konkrétní chybu není program předem připravený,
224+
ukončí se celý program.
232225

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

0 commit comments

Comments
 (0)