Mine is the python debugger. I was a long holdout thinking that print statements were sufficient. That was untill I started having errors crop up in functions that took minutes to run. The thing that I most notably wish I would have known about is post_mortem.

Example

[ins] In [4]: def repeater(msg, repeats=1):
         ...:     "repeats messages {repeats} number of times"
         ...:     print(f'{msg}\n' * repeats)

[ins] In [5]: repeater('hi', 3) hi hi hi

[ins] In [6]: repeater('hi', 'a') #

TypeError Traceback (most recent call last) <ipython-input-6-0ec595774c81> in <module> ----> 1 repeater('hi', 'a')

<ipython-input-4-530890de75cd> in repeater(msg, repeats) 1 def repeater(msg, repeats=1): 2 "repeats messages {repeats} number of times" ----> 3 print(f'{msg}\n' * repeats) 4

Debug with iPython/Jupyter

%debug

Vanilla Debug

import pdb
import sys

pdb.post_mortem(sys.last_traceback)

More

For more information about the debugger checkout the real python article. https://realpython.com/python-debugging-pdb/

Also keep a bookmark of the table of pdb commands from the article https://realpython.com/python-debugging-pdb/#essential-pdb-commands

Debug Session

debug session