4. Debugging#

Code always eventually breaks. Let’s look at some broken code:

from typing import Tuple
import sys
def broken() -> None:
    1 / 0
def my_broken_function() -> Tuple[int, int]:
    x = 6
    y = 4
    x += 2
    y *= 2
    x -= y
    y /= x
    return x, y
my_broken_function()
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
Cell In[5], line 1
----> 1 my_broken_function()

Cell In[4], line 7, in my_broken_function()
      5 y *= 2
      6 x -= y
----> 7 y /= x
      8 return x, y

ZeroDivisionError: division by zero

4.1. IPython debugger#

Try writing %debug into the cell below! (you can even skip the %)

# %debug

The mini-language here is pdb, and is similar to gdb and many other debuggers. You can step forward, up, etc. You can set breakpoints, or in Python 3.7+, you can just write breakpoint() anywhere, and the “current” debugger will pick up there!

4.2. Rich tracebacks#

Another trick comes from the Rich library. You can install a nicer traceback handler. Never do this in a library, but only in applications and user code.

import rich.traceback

rich.traceback.install(show_locals=True)
<bound method InteractiveShell.excepthook of <ipykernel.zmqshell.ZMQInteractiveShell object at 0x7f69210c02b0>>

This needs to be in a file (normally it will be) for the traceback to show up nicely:

%%writefile tmp_rich.py
def my_broken_function():
    x = 6
    y = 4
    x += 2
    y *= 2
    x -= y
    y /= x
    return x, y
Writing tmp_rich.py
import tmp_rich

tmp_rich.my_broken_function()
╭─────────────────────────────── Traceback (most recent call last) ────────────────────────────────╮
 in <module>:3                                                                                    
                                                                                                  
   1 import tmp_rich                                                                              
   2                                                                                              
 3 tmp_rich.my_broken_function()                                                                
   4                                                                                              
                                                                                                  
 ╭─────────────────────────────────────────── locals ───────────────────────────────────────────╮ 
              broken = <function broken at 0x7f69210e91b0>                                      
                exit = <IPython.core.autocall.ZMQExitAutocall object at 0x7f69210c10c0>         
         get_ipython = <bound method InteractiveShell.get_ipython of                            
                       <ipykernel.zmqshell.ZMQInteractiveShell object at 0x7f69210c02b0>>       
                  In = [                                                                        
                       '',                                                                  
                       '# WebAssembly version using Pyodide!\n# The following code is       
                       specific to the Pyo'+124,                                                
                       'from typing import Tuple\nimport sys',                              
                       'def broken() -> None:\n    1 / 0',                                  
                       'def my_broken_function() -> Tuple[int, int]:\n    x = 6\n    y =    
                       4\n    x += 2\n    '+44,                                                 
                       'my_broken_function()',                                              
                       '# %debug',                                                          
                       'import                                                              
                       rich.traceback\n\nrich.traceback.install(show_locals=True)',             
                       "get_ipython().run_cell_magic('writefile', 'tmp_rich.py', 'def       
                       my_broken_function"+94,                                                  
                       'import tmp_rich\n\ntmp_rich.my_broken_function()'                   
                       ]                                                                        
  my_broken_function = <function my_broken_function at 0x7f69210e9240>                          
                open = <function open at 0x7f692420bb50>                                        
                 Out = {                                                                        
                       7: <bound method InteractiveShell.excepthook of                      
                       <ipykernel.zmqshell.ZMQInteractiveShell object at 0x7f69210c02b0>>       
                       }                                                                        
                quit = <IPython.core.autocall.ZMQExitAutocall object at 0x7f69210c10c0>         
                rich = <module 'rich' from                                                      
                       '/usr/share/miniconda3/envs/level-up-your-python/lib/python3.10/site-p…  
                 sys = <module 'sys' (built-in)>                                                
            tmp_rich = <module 'tmp_rich' from                                                  
                       '/home/runner/work/level-up-your-python/level-up-your-python/notebooks…  
               Tuple = typing.Tuple                                                             
 ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ 
                                                                                                  
 /home/runner/work/level-up-your-python/level-up-your-python/notebooks/tmp_rich.py:7 in           
 my_broken_function                                                                               
                                                                                                  
   4 x += 2                                                                                   
   5 y *= 2                                                                                   
   6 x -= y                                                                                   
 7 y /= x                                                                                   
   8 return x, y                                                                              
   9                                                                                              
                                                                                                  
 ╭─ locals ─╮                                                                                     
  x = 0                                                                                         
  y = 8                                                                                         
 ╰──────────╯                                                                                     
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
ZeroDivisionError: division by zero

4.3. Debugging in Jupyter Lab#

This used to require the Xeus Python kernel instead of IPython, but IPyKernel 6+ now supports the visual debugger protocol directly.

Turn on the debugger with the switch on the top right. Click on the line numbers to set a breakpoint. Then run.

def my_broken_function():
    # breakpoint()
    x = 6
    y = 4
    x += 2
    y *= 2
    x -= y
    y /= x
    return x, y
my_broken_function()
╭─────────────────────────────── Traceback (most recent call last) ────────────────────────────────╮
 in <module>:1                                                                                    
                                                                                                  
 1 my_broken_function()                                                                         
   2                                                                                              
                                                                                                  
 ╭─────────────────────────────────────────── locals ───────────────────────────────────────────╮ 
              broken = <function broken at 0x7f69210e91b0>                                      
                exit = <IPython.core.autocall.ZMQExitAutocall object at 0x7f69210c10c0>         
         get_ipython = <bound method InteractiveShell.get_ipython of                            
                       <ipykernel.zmqshell.ZMQInteractiveShell object at 0x7f69210c02b0>>       
                  In = [                                                                        
                       '',                                                                  
                       '# WebAssembly version using Pyodide!\n# The following code is       
                       specific to the Pyo'+124,                                                
                       'from typing import Tuple\nimport sys',                              
                       'def broken() -> None:\n    1 / 0',                                  
                       'def my_broken_function() -> Tuple[int, int]:\n    x = 6\n    y =    
                       4\n    x += 2\n    '+44,                                                 
                       'my_broken_function()',                                              
                       '# %debug',                                                          
                       'import                                                              
                       rich.traceback\n\nrich.traceback.install(show_locals=True)',             
                       "get_ipython().run_cell_magic('writefile', 'tmp_rich.py', 'def       
                       my_broken_function"+94,                                                  
                       'import tmp_rich\n\ntmp_rich.my_broken_function()',                  
                       ... +2                                                               
                       ]                                                                        
  my_broken_function = <function my_broken_function at 0x7f6918909c60>                          
                open = <function open at 0x7f692420bb50>                                        
                 Out = {                                                                        
                       7: <bound method InteractiveShell.excepthook of                      
                       <ipykernel.zmqshell.ZMQInteractiveShell object at 0x7f69210c02b0>>       
                       }                                                                        
                quit = <IPython.core.autocall.ZMQExitAutocall object at 0x7f69210c10c0>         
                rich = <module 'rich' from                                                      
                       '/usr/share/miniconda3/envs/level-up-your-python/lib/python3.10/site-p…  
                 sys = <module 'sys' (built-in)>                                                
            tmp_rich = <module 'tmp_rich' from                                                  
                       '/home/runner/work/level-up-your-python/level-up-your-python/notebooks…  
               Tuple = typing.Tuple                                                             
 ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ 
                                                                                                  
 in my_broken_function:8                                                                          
                                                                                                  
    5 x += 2                                                                                  
    6 y *= 2                                                                                  
    7 x -= y                                                                                  
  8 y /= x                                                                                  
    9 return x, y                                                                             
   10                                                                                             
                                                                                                  
 ╭─ locals ─╮                                                                                     
  x = 0                                                                                         
  y = 8                                                                                         
 ╰──────────╯                                                                                     
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
ZeroDivisionError: division by zero