Python Debug Cheatsheet

A collection of my favorite Python debugging options

Peter Xie
4 min readJul 5, 2020
Photo by C M on Unsplash

Print

import time
print(time.asctime(),'debugging ...')

Output:

Sat Jul  4 13:45:52 2020 debugging ...

Logging

Basic

import logging
logging.basicConfig(filename = 'a.log')
log = logging.getLogger()
log.error('Some error')

cat a.log:

ERROR:root:Some error

More Config

import logging# DEBUG, INFO, WARNING, ERROR, CRITICAL
log_level = logging.INFO
logging.basicConfig(filename = 'b.log',
level=log_level,
filemode='w', # or 'a'
format='%(asctime)s %(levelname)s: %(message)s',
)
log = logging.getLogger()
log.info('Some info log')
log.debug("Won't print at INFO level")

cat b.log:

2020-07-04 17:50:42,953 INFO: Some info log

Multiple Log Files

Use different logger names for different log files.

import logginglog_level = logging.INFO
def create_logger(filename, logname=''):
handler = logging.FileHandler(filename)
formatter = logging.Formatter(
'%(asctime)s %(levelname)s: %(message)s'
)
handler.setFormatter(formatter)
log = logging.getLogger(logname)
log.setLevel(log_level)
log.addHandler(handler)
return log
# 1st log file
log1 = create_logger('a.log','app1')
log1.info('app1 info')
# 2nd log file
log2 = create_logger('b.log','app2')
log2.error('app2 error')

cat a.log:

2020-07-04 18:08:41,900 INFO: app1 info

cat b.log:

2020-07-04 18:08:41,900 ERROR: app2 error

ipdb

Breakpoint in file

ipdb is an enhanced version of pdb (built-in). It supports all pdb commands and is simply easier to use like tab for auto-complete.

Install: pip install ipdb

ipdb command cheatsheet. Check here for more commands.

n[ext]:         next line, step over
s[tep]: step into next line or function.
c[ontinue]: continue
until [lineno]: continue execution until a line with a number greater or equal to lineno is reached
l[ine]: show more lines
p <variable>: print value
q[uit]: quit debugging
help: show all commands
help <command>: help of a command

Use ipdb.set_trace() to set a breakpoint.
Example:

# a.py
import ipdb
a = 1
ipdb.set_trace()
b = 2
r = a / b

Run python a.py:

ipdb example

ipdb cli

You can also debug your code without setting breakpoints in the file by:

# Start to execute from 1st line of the code a.py 
# and you can use n or other ipdb commands to continue.
python -m ipdb a.py

# Start debug and pause at line 10
python -m ipdb -c 'until 10' a.py

# Don't exit the program on Exception and enter debug mode instead
# If no Exception, it finishes the program once and restarts at 1st line
python -m ipdb -c 'continue' a.py

Interactive Mode

It is the same as python -m ipdb -c ‘continue’ a.py. Python does not exit after an Exception.

# a.py
def divide(a,b):
return a / b
b = 0
divide(1,b)

Run python -i a.py

python -i a.py
Traceback (most recent call last):
File "a.py", line 5, in <module>
divide(1,b)
File "a.py", line 2, in divide
return a / b
ZeroDivisionError: division by zero
>>> b
0
>>>

Exception

Understand Exception

def divide(a,b):
return a / b
divide(1,0)

Run and you will get an exception as follows.

The last line is the exception or root cause in other words.

Above that is so-called traceback. It traces the source of the exception. The error occurs in line #4 divide(1,0) , but the source is in line #2 inside function divide and you see that at the bottom of the traceback.

Print Exception

It is a good practice to specify the Error, i.e. ZeroDivisionError, if you want what to capture, otherwise you can use generic except Exception as ex.

def divide(a,b):
return a / b
try:
divide(1,0)
except ZeroDivisionError as ex:
print(ex)

Output:

division by zero

Print Full Traceback

import traceback
def divide(a,b):
return a / b
try:
divide(1,0)
except Exception:
print(traceback.format_exc())

Output:

Traceback (most recent call last):
File "<ipython-input-2-251e5476aec8>", line 6, in <module>
divide(1,0)
File "<ipython-input-2-251e5476aec8>", line 3, in divide
return a / b
ZeroDivisionError: division by zero

Jupyter Notebook

Jupyter notebook is one of my favorite debugging tools. It is like a visual version of ipdb.

There is an implicit breakpoint after each cell (a block) of code. You can check/change variable values and continue with another block of code in the next cell.

Ctrl + Enter to run a cell.

Example:

Jupyter Notebook Example

VS Code Magic to Jupyter

Add #%% to an existing python file (.py) and you can run it as a Jupyter cell.

trace

The built-in trace module can print what lines are executed (similar to bash -x option) and the timing option is handy to debug performance issues on line level.

Example a.py:

import time
print('line 1')
time.sleep(0.2)
if 1 == 1:
print('ok')

Run with trace:

python -m trace -t --timing \
--ignore-dir=$(python -c "import sys,os; print((os.pathsep).join(sys.path[1:]))") \
a.py
Output:
--- modulename: a, funcname: <module>
0.00 a.py(1): import time
0.00 a.py(2): print('line 1')
line 1
0.00 a.py(3): time.sleep(0.2)
0.20 a.py(4): if 1 == 1:
0.20 a.py(5): print('ok')
ok

Notes: The ignore-dir parameter is set to not trace system paths, i.e. standard libraries.

VS Code F5?

VS code F5 Start Debugging is nice when it works, BUT it does not work for me most of the time. Note it uses debugpy under the hood.

References

https://github.com/dabeaz-course/practical-python/blob/master/Notes/08_Testing_debugging/03_Debugging.md
https://www.pythoncheatsheet.org/#Debugging
https://docs.python.org/3/library/debug.html

--

--