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