Six Debugging Techniques for Python Programmers

Introduction
It’s nearly impossible that a program can be written once and run perfectly all the time. Bugs and debugging are part of the daily lives of programmers. Therefore, to learn some useful debugging techniques is an important task for every developer. This post will introduce six debugging techniques for Python developers.
1. Print and Check
The simplest but powerful method is to print some particular variables and check their values are as expected or not.
As the above example shown, if we are not sure with a variable, we can just print and check it. After that, some modifications can be considered.
The biggest disadvantage of using print
is that you have to delete it in the future. The program will be hard to read if there are print
statements everywhere and the results will also contain a lot of garbage information. No worries, we have other techniques.
2. Assert and Check
Wherever the print
is used to assist debugging, assert
can be used instead.
As the example shown, Python’s build-in assert
method can raise an AssertionError
if its statement is False
.
Python gives us more flexibility when using assert
. We can use the -0
parameter to close all the assert
statements in the program when starting the Python interpreter. After that, all the assert
methods will not work.
python -0 assert.py
# Traceback (most recent call last):
# ...
# ValueError: invalid literal for int() with base 10: 'Yang'
Although the Assert and Check method is a little flexible than the Print and Check, lots of assert
statements will also make our code a bit confusing and unreadable.
3. Using logging Module
Python has an important module named logging
. Replacing print
or assert
to logging methods is more professional and powerful way to debug. Actually, it is a “heavy weapon” for a Python project. Because it gives us lots of flexibility and abilities to debug, record and monitor our programs.
For example, through simple configuration, a statement can be output to different places, such as console and files.
The above example shows that we can print logging messages to a file rather than console. It’s pretty useful if there is much information and inconvenient to look at them in the console. Not to mention it’s also helpful to look for logging history when they are saves in a file.
4. pdb
The fourth way is to start the Python debugger pdb, let the program run in a single step mode, and you can check the running status at any time. The pdb is a Python’s build-in debugger tool. There are some other debugger tools such as web-pdb, pudb and so on. In most cases, the pdb is useful enough.
A Basic Example
For example, we use pdb to run the following program step by step:
s = '0'
n = int(s)
print(1000 / n)
Firstly, we start the pdb in terminal:
$ python -m pdb test.py
Now our program are running from the first line:
> /home/yang/test.py(1)<module>()
-> s = '0'
We can check the full code using command l
:
(Pdb) l
1 -> s = '0'
2 n = int(s)
3 print(1000 / n)
Enter the command n
to run next line of our code:
(Pdb) n
> /home/yang/test.py(2)<module>()
-> n = int(s)
(Pdb) n
> /home/yang/test.py(3)<module>()
-> print(1000 / n)
(Pdb) n
ZeroDivisionError: division by zero
> /home/yang/test.py(3)<module>()
-> print(1000 / n)
At any time, we can enter the command p
and a variable name to check this value is correct or not:
(Pdb) p s
'0'
(Pdb) p n
0
Set Trace in Code
If there are lots of lines of our code, starting from the first line to use the pdb is very time consuming. We can use the function pdb.set_trace()
to set a breakpoint in our code. For example:
import pdb
s = '0'
n = int(s)
pdb.set_trace()
# The program will be paused and start pdb debugger
print(1000 / n)
If we run the above program, the result is:
> /home/yang/test.py(7)<module>()
-> print(1000 / n)
(Pdb)
Therefore, we can run the pdb from line the line of print(1000/n)
.
A More Convenient Way
From Python 3.7, there is a build-in function breakpoint()
which can totally replace the pdb.set_trace()
.
s = '0'
n = int(s)
breakpoint()
print(1000 / n)
The result is identical with the result of using pdb.set_trace()
. Because breakpoint()
calls sys.breakpointhook()
function and this function calls pdb.set_trace()
by default.
There are some benefits of using breakpoint()
:
- Obviously, using
breakpoint()
provides a little convenience because we don’t have to explicitly importpdb
module. - More importantly, if we don’t like pdb and would like to use other debugger tools to replace it, there is no need to change our code.
For example, if we gonna use the web-pdb
(or other debugger tools) which may be better than the pdb
. The traditional method let us have to change every pdb.set_trace()
statement to web_pdb.set_trace()
(or other statement based on which tool are being used) in our code. Python should be elegant but this job is boring and not elegant at all.
Using breakpoint()
, no need to change code. We just need to change the environment variable PYTHONBREAKPOINT
at once.
$ PYTHONBREAKPOINT = web_pdb.set_trace python3.7
We can also set this variable to zero, then all the breakpoints will be ignored and our program will run normally rather than start the debugger tool.
$ PYTHONBREAKPOINT = 0 python3.7
5. Integrated Development Environment
If you want to set breakpoints and single-step execution more comfortably, you need an IDE (Integrated development environment) that supports debugging.
The current good Python IDEs are:
- Visual Studio Code: Python plug-in needs to be installed.
- PyCharm: Great IDE designed for Python developers. By the way, this is my favourite Python IDE.
- Eclipse plus the “pydev” plug-in can also debug Python programs.
- … …
6. Pen and Paper
It sounds like a really traditional method but it can’t be ignored. Sometimes your mind is not very clear, writing it down with paper and pen is a good way to straighten out your thinking.
For example, I debugged a large project developed by others last year. There are lots of classes and complicated inheritance relationships between them. Unfortunately, there is no documents and even no code comments. In order to understand the relationships of classes, I wrote down the classes’ names and drew a draft diagram to help me sort their relationships out. After that, I found which one was wrong and caused the bug.
Conclusion
Debugging is programmers everyday life. We can use all different debugging tricks to make our life easier.
Please leave your comments if you have some interesting debugging tricks.
Thanks for reading. More Python related posts are here: