Skip to content

My Python Debug Cheatsheet

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 logging
log_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
q[uit]:         quit debugging
help:           show all commands
help <command>: help of a command

Use ipdb.set_trace() to set a breakpoint. For example:

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

Run python a.py:

$ python a.py
> /home/peter/a.py(4)<module>()
      3 ipdb.set_trace()
----> 4 b = 2
      5 r = a / b

ipdb> n
> /home/peter/a.py(5)<module>()
      3 ipdb.set_trace()
      4 b = 2
----> 5 r = a / b

ipdb> print(b)
2

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

-i interactive mode 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 with -i:

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.

pdb traceback screenshot

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

The above is the 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 the function divide and you see that at the bottom of the traceback.

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

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