Jak Python loví š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.

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.

In [3]:
# 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.

In [4]:
fact_b(4)
> <ipython-input-3-d3bccfe82788>(21)fact_b()
-> r = fact(nn)
(Pdb) help

Documented commands (type help <topic>):
========================================
EOF    c          d        h         list      q        rv       undisplay
a      cl         debug    help      ll        quit     s        unt      
alias  clear      disable  ignore    longlist  r        source   until    
args   commands   display  interact  n         restart  step     up       
b      condition  down     j         next      return   tbreak   w        
break  cont       enable   jump      p         retval   u        whatis   
bt     continue   exit     l         pp        run      unalias  where    

Miscellaneous help topics:
==========================
exec  pdb

(Pdb) q
---------------------------------------------------------------------------
BdbQuit                                   Traceback (most recent call last)
<ipython-input-4-6b014a0b29a4> in <module>()
----> 1 fact_b(4)

<ipython-input-3-d3bccfe82788> in fact_b(nn)
     19     # tade se běh programu zastaví a objeví se pdb rozhraní
     20     set_trace()
---> 21     r = fact(nn)
     22     return r

<ipython-input-3-d3bccfe82788> in fact_b(nn)
     19     # tade se běh programu zastaví a objeví se pdb rozhraní
     20     set_trace()
---> 21     r = fact(nn)
     22     return r

/sw/python2/anaconda3/envs/python_course/lib/python3.4/bdb.py in trace_dispatch(self, frame, event, arg)
     46             return # None
     47         if event == 'line':
---> 48             return self.dispatch_line(frame)
     49         if event == 'call':
     50             return self.dispatch_call(frame, arg)

/sw/python2/anaconda3/envs/python_course/lib/python3.4/bdb.py in dispatch_line(self, frame)
     65         if self.stop_here(frame) or self.break_here(frame):
     66             self.user_line(frame)
---> 67             if self.quitting: raise BdbQuit
     68         return self.trace_dispatch
     69 

BdbQuit: 

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

In [6]:
fact_b(4)
> <ipython-input-3-d3bccfe82788>(21)fact_b()
-> r = fact(nn)
(Pdb) dir()
['nn', 'set_trace']
(Pdb) nn = 10
(Pdb) c
Out[6]:
3628800

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.

In [7]:
%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.).

In [8]:
%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.

In [9]:
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.

Další možnosti

  • Vylepšení pdb: pydb a jeho následovníci pydbgr a trepan.
  • Vynikající je debugger v Pydev.
  • 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.

Komentáře

Comments powered by Disqus