13. Jak Python lovi štěnice#
Chyby neboli bugs (štěnice) jsou v každém programu (téměř). Naučíme se je chytat za běhu pomocí nástroje zvaného debugger (“odstraňovač štěnic”). Navíc si ukážeme, jaké jsou výhody dynamického interpretovaného jazyka, jakým je Python.
13.1. Pdb – základní debugger#
Pdb je základní debugger pro Python. Základní použití je vložit break pointu do vašeho kódu pomocí set_trace
.
# definice fact
def fact(nn):
"""Spočítá faktoriál
"""
from math import factorial
try:
# zkusíme převést n na číslo
nn = float(nn)
except ValueError as e:
raise ValueError("{} není číslo".format(nn))
# je n celé číslo >= 0
if not nn.is_integer() or nn < 0:
raise ValueError("{} není celé číslo >= 0".format(nn))
return factorial(nn)
# nová funkce s break pointem
def fact_b(nn):
from pdb import set_trace
# tade se běh programu zastaví a objeví se pdb rozhraní
set_trace()
r = fact(nn)
return r
Tady vidíme, jak vypadá interace s pdb. Všímejte si řádků (Pdb) a příkazů do nich zadaných: help
, where
, step
, continue
. To jsou příkazy pro samotný debugger. Kromě toho ale můžeme zadávat i příkazy Pythonu, jako např. dir()
. Toto je velice důležité! V debuggeru máme k dispozici celý Python, všechny jeho funkce atd.
fact_b(4)
> /var/folders/dm/gbbql3p121z0tr22r2z98vy00000gn/T/ipykernel_75722/1605037290.py(21)fact_b()
18 from pdb import set_trace
19 # tade se běh programu zastaví a objeví se pdb rozhraní
20 set_trace()
---> 21 r = fact(nn)
22 return r
Dokonce můžeme za běhu měnit proměnné! Podívejte se, jak jsme do nn uložili 10 a že výsledek tomu odpovídá.
fact_b(4)
> <ipython-input-3-d3bccfe82788>(21)fact_b()
-> r = fact(nn)
(Pdb) dir()
['nn', 'set_trace']
(Pdb) nn = 10
(Pdb) c
3628800
13.2. IPython debugger#
Lze asi očekávat, že existují i další možnosti chytání štěnic. Podívejme se, co nabízí náš dobrý známý IPython.
%debug
#
%debug
(případně %%debug
pro celou buňku) spustí příkaz v debug módu, který se spustí, ještě než se začne cokoli provádět. V ipdb např. funguje doplňování pomocí tabelátoru (to ovšem nefunguje v notebooku), jinak je ale téměř totožný s pdb.
%debug fact(1)
NOTE: Enter 'c' at the ipdb> prompt to continue execution.
> <string>(1)<module>()
ipdb> help
Documented commands (type help <topic>):
========================================
EOF cl disable interact pdef quit source up
a clear display j pdoc r step w
alias commands down jump pfile restart tbreak whatis
args condition enable ll pinfo return u where
b cont exit longlist pinfo2 retval unalias
break continue h n pp run undisplay
bt d help next psource rv unt
c debug ignore p q s until
Miscellaneous help topics:
==========================
exec pdb
Undocumented commands:
======================
l list
ipdb> q
%pdb#
Pomocí %pdb on
zapneme automatické vyvolání ipdb ve chvíli vyhození výjimky. To je pochopitelně velice užitečné, protože můžeme začít debugovat právě ve chvíli, kdy nastala chyba. Navíc máme k dispozici veškerý kontext (obsah paměti, historii volání (stack trace) apod.).
%pdb on
fact(4.1)
Automatic pdb calling has been turned ON
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-8-9b953992abcd> in <module>()
1 get_ipython().magic('pdb on')
----> 2 fact(4.1)
<ipython-input-3-d3bccfe82788> in fact(nn)
11 # je n celé číslo >= 0
12 if not nn.is_integer() or nn < 0:
---> 13 raise ValueError("{} není celé číslo >= 0".format(nn))
14 return factorial(nn)
15
ValueError: 4.1 není celé číslo >= 0
> <ipython-input-3-d3bccfe82788>(13)fact()
12 if not nn.is_integer() or nn < 0:
---> 13 raise ValueError("{} není celé číslo >= 0".format(nn))
14 return factorial(nn)
ipdb> dir()
['factorial', 'nn']
ipdb> q
Break pointy jinak#
Někdy (často) není praktické nastavovat break pointy pomocí set_trace
. (i)pdb nám umožňují nastavit break pointy pomocí čísla řádku (v libovolném souboru) nebo jména funkce. Případně můžeme přidat ještě podmínku.
Pro ukázku definujeme funkci, která vrací (nn!)^2. Spustíme jí v debug módu a umístíme break point do funkce fact pomocí break fact
.
def fact2(nn):
r = fact(nn)**2
return r
%debug fact2(4)
NOTE: Enter 'c' at the ipdb> prompt to continue execution.
> <string>(1)<module>()
ipdb> break fact
Breakpoint 1 at <ipython-input-3-d3bccfe82788>:2
ipdb> c
> <ipython-input-3-d3bccfe82788>(5)fact()
4 """
----> 5 from math import factorial
6 try:
ipdb> where
/sw/python2/anaconda3/envs/python_course/lib/python3.4/bdb.py(431)run()
429 sys.settrace(self.trace_dispatch)
430 try:
--> 431 exec(cmd, globals, locals)
432 except BdbQuit:
433 pass
<string>(1)<module>()
<ipython-input-9-b8ef3a5b0033>(2)fact2()
1 def fact2(nn):
----> 2 r = fact(nn)**2
3 return r
4
5 get_ipython().magic('debug fact2(4)')
> <ipython-input-3-d3bccfe82788>(5)fact()
3 """Spočítá faktoriál
4 """
----> 5 from math import factorial
6 try:
7 # zkusíme převést n na číslo
ipdb> q
IPython embed#
Toto není přísně řečeno debugování, ale má to hodně společného. IPython umožňuje spustit přímou interakci s aktuálním kontextem pomocí funkce embed
, případně embed_kernel
. Na rozdíl od pdb se ale změny nepropagují zpět do spuštěcího kontextu, takže jakékoli změny se po ukončení ztratí. Více v dokumentaci.
13.3. Další možnosti#
Vylepšení pdb: python3-trepan.
Vynikající je debugger v PyCharm nebo ve VS Code.
pudb je “grafický” debugger v textovém režimu.
Většina vývojových prostředí nějakým způsobem umožňuje pustit debugger.