Python 101 - Debugging Your Code with pdb

Mistakes in your code are known as "bugs". You will make mistakes. You will make many mistakes, and that's totally fine. Most of the time, they will be simple mistakes such as typos. But since computers are very literal, even typos prevent your code from working as intended. So they need to be fixed. The process of fixing your mistakes in programming is known as debugging.

The Python programming language comes with its own built-in debugger called pdb. You can use pdb on the command line or import it as a module. The name, pdb, is short for "Python debugger".

Here is a link to the full documentation for pdb:

In this article, you will familiarize yourself with the basics of using pdb. Specifically, you will learn the following:

  • Starting pdb in the REPL
  • Starting pdb on the Command Line
  • Stepping Through Code
  • Adding Breakpoints in pdb
  • Creating a Breakpoint with set_trace()
  • Using the built-in breakpoint() Function
  • Getting Help

While pdb is handy, most Python editors have debuggers with more features. You will find the debugger in PyCharm or WingIDE to have many more features, such as auto-complete, syntax highlighting, and a graphical call stack.

A call stack is what your debugger will use to keep track of function and method calls. When possible, you should use the debugger that is included with your Python IDE as it tends to be a little easier to understand.

However, there are times where you may not have your Python IDE, for example when you are debugging remotely on a server. It is those times when you will find pdb to be especially helpful.

Let's get started!

Starting pdb in the REPL

The best way to start is to have some code that you want to run pdb on. Feel free to use your own code or a code example from another article on this blog.

Or you can create the following code in a file named debug_code.py:

# debug_code.py

def log(number):
    print(f'Processing {number}')
    print(f'Adding 2 to number: {number + 2}')
    

def looper(number):
    for i in range(number):
        log(i)
        
if __name__ == '__main__':
    looper(5)

There are several ways to start pdb and use it with your code. For this example, you will need to open up a terminal (or cmd.exe if you're a Windows user). Then navigate to the folder that you saved your code to.

Now start Python in your terminal. This will give you the Python REPL where you can import your code and run the debugger, pdb. Here's how:

>>> import debug_code
>>> import pdb
>>> pdb.run('debug_code.looper(5)')
> <string>(1)<module>()
(Pdb) continue
Processing 0
Adding 2 to number: 2
Processing 1
Adding 2 to number: 3
Processing 2
Adding 2 to number: 4
Processing 3
Adding 2 to number: 5
Processing 4
Adding 2 to number: 6

The first two lines of code import your code and pdb. To run pdb against your code, you need to use pdb.run() and tell it what to do. In this case, you pass in debug_code.looper(5) as a string. When you do this, the pdb module will transform the string into an actual function call of debug_code.looper(5).

The next line is prefixed with (Pdb). That means you are now in the debugger. Success!

To run your code in the debugger, type continue or c for short. This will run your code until one of the following happens:

  • The code raises an exception
  • You get to a breakpoint (explained later on in this article)
  • The code finishes

In this case, there were no exceptions or breakpoints set, so the code worked perfectly and finished execution!

Starting pdb on the Command Line

An alternative way to start pdb is via the command line. The process for starting pdb in this manner is similar to the previous method. You still need to open up your terminal and navigate to the folder where you saved your code.

But instead of opening Python, you will run this command:

python -m pdb debug_code.py

When you run pdb this way, the output will be slightly different:

> /python101code/chapter26_debugging/debug_code.py(1)<module>()
-> def log(number):
(Pdb) continue
Processing 0
Adding 2 to number: 2
Processing 1
Adding 2 to number: 3
Processing 2
Adding 2 to number: 4
Processing 3
Adding 2 to number: 5
Processing 4
Adding 2 to number: 6
The program finished and will be restarted
> /python101code/chapter26_debugging/debug_code.py(1)<module>()
-> def log(number):
(Pdb) exit

The 3rd line of output above has the same (Pdb) prompt that you saw in the previous section. When you see that prompt, you know you are now running in the debugger. To start debugging, enter the continue command.

The code will run successfully as before, but then you will see a new message:

The program finished and will be restarted

The debugger finished running through all your code and then started again from the beginning! That is handy for running your code multiple times! If you do not wish to run through the code again, you can type exit to quit the debugger.

Stepping Through Code

Stepping through your code is when you use your debugger to run one line of code at a time. You can use pdb to step through your code by using the step command, or s for short.

Following is the first few lines of output that you will see if you step through your code with pdb:

$ python -m pdb debug_code.py 
> /python101code/chapter26_debugging/debug_code.py(3)<module>()
-> def log(number):
(Pdb) step
> /python101code/chapter26_debugging/debug_code.py(8)<module>()
-> def looper(number):
(Pdb) s
> /python101code/chapter26_debugging/debug_code.py(12)<module>()
-> if __name__ == '__main__':
(Pdb) s
> /python101code/chapter26_debugging/debug_code.py(13)<module>()
-> looper(5)
(Pdb)

The first command that you pass to pdb is step. Then you use s to step through the following two lines. You can see that both commands do exactly the same, since "s" is a shortcut or alias for "step".

You can use the next (or n) command to continue execution until the next line within the function. If there is a function call within your function, next will step over it. What that means is that it will call the function, execute its contents, and then continue to the next line in the current function. This, in effect, steps over the function.

You can use step and next to navigate your code and run various pieces efficiently.

If you want to step into the looper() function, continue to use step. On the other hand, if you don't want to run each line of code in the looper() function, then you can use next instead.

You should continue your session in pdb by calling step so that you step into looper():

(Pdb) s
--Call--
> /python101code/chapter26_debugging/debug_code.py(8)looper()
-> def looper(number):
(Pdb) args
number = 5

When you step into looper(), pdb will print out --Call-- to let you know that you called the function. Next you used the args command to print out all the current args in your namespace. In this case, looper() has one argument, number, which is displayed in the last line of output above. You can replace args with the shorter a.

The last command that you should know about is jump or j. You can use this command to jump to a specific line number in your code by typing jump followed by a space and then the line number that you wish to go to.

Now let's learn how you can add a breakpoint!

Adding Breakpoints in pdb

A breakpoint is a location in your code where you want your debugger to stop so you can check on variable states. What this allows you to do is to inspect the callstack, which is a fancy term for all variables and function arguments that are currently in memory.

If you have PyCharm or WingIDE, then they will have a graphical way of letting you inspect the callstack. You will probably be able to mouse over the variables to see what they are set to currently. Or they may have a tool that lists out all the variables in a sidebar.

Let's add a breakpoint to the last line in the looper() function which is line 10.

Here is your code again:

# debug_code.py

def log(number):
    print(f'Processing {number}')
    print(f'Adding 2 to number: {number + 2}')
    

def looper(number):
    for i in range(number):
        log(i)
        
if __name__ == '__main__':
    looper(5)

To set a breakpoint in the pdb debugger, you can use the break or b command followed by the line number you wish to break on:

$ python3.8 -m pdb debug_code.py 
> /python101code/chapter26_debugging/debug_code.py(3)<module>()
-> def log(number):
(Pdb) break 10
Breakpoint 1 at /python101code/chapter26_debugging/debug_code.py:10
(Pdb) continue
> /python101code/chapter26_debugging/debug_code.py(10)looper()
-> log(i)
(Pdb)

Now you can use the args command here to find out what the current arguments are set to. You can also print out the value of variables, such as the value of i, using the print (or p for short) command:

(Pdb) print(i)
0

Now let's find out how to add a breakpoint to your code!

Creating a Breakpoint with set_trace()

The Python debugger allows you to import the pbd module and add a breakpoint to your code directly, like this:

# debug_code_with_settrace.py

def log(number):
    print(f'Processing {number}')
    print(f'Adding 2 to number: {number + 2}')


def looper(number):
    for i in range(number):
        import pdb; pdb.set_trace()
        log(i)

if __name__ == '__main__':
    looper(5)

Now when you run this code in your terminal, it will automatically launch into pdb when it reaches the set_trace() function call:

$ python3.8 debug_code_with_settrace.py 
> /python101code/chapter26_debugging/debug_code_with_settrace.py(12)looper()
-> log(i)
(Pdb)

This requires you to add a fair amount of extra code that you'll need to remove later. You can also have issues if you forget to add the semi-colon between the import and the pdb.set_trace() call.

To make things easier, the Python core developers added breakpoint() which is the equivalent of writing import pdb; pdb.set_trace().

Let's discover how to use that next!

Using the built-in breakpoint() Function

Starting in Python 3.7, the breakpoint() function has been added to the language to make debugging easier. You can read all about the change here:

Go ahead and update your code from the previous section to use breakpoint() instead:

# debug_code_with_breakpoint.py

def log(number):
    print(f'Processing {number}')
    print(f'Adding 2 to number: {number + 2}')


def looper(number):
    for i in range(number):
        breakpoint()
        log(i)

if __name__ == '__main__':
    looper(5)

Now when you run this in the terminal, Pdb will be launched exactly as before.

Another benefit of using breakpoint() is that many Python IDEs will recognize that function and automatically pause execution. This means you can use the IDE's built-in debugger at that point to do your debugging. This is not the case if you use the older set_trace() method.

Getting Help

This chapter doesn't cover all the commands that are available to you in pdb. So to learn more about how to use the debugger, you can use the help command within pdb. It will print out the following:

(Pdb) help

Documented commands (type help <topic>):
========================================
EOF    c          d        h         list      q        rv       undisplay
a      cl         debug    help      ll        quit     s        unt      
alias  clear      disable  ignore    longlist  r        source   until    
args   commands   display  interact  n         restart  step     up       
b      condition  down     j         next      return   tbreak   w        
break  cont       enable   jump      p         retval   u        whatis   
bt     continue   exit     l         pp        run      unalias  where    

Miscellaneous help topics:
==========================
exec  pdb

If you want to learn what a specific command does, you can type help followed by the command.

Here is an example:

(Pdb) help where
w(here)
        Print a stack trace, with the most recent frame at the bottom.
        An arrow indicates the "current frame", which determines the
        context of most commands.  'bt' is an alias for this command.

Go give it a try on your own!

Wrapping Up

Being able to debug your code successfully takes practice. It is great that Python provides you with a way to debug your code without installing anything else. You will find that using breakpoint() to enable breakpoints in your IDE is also quite handy.

In this article you learned about the following:

  • Starting pdb in the REPL
  • Starting pdb on the Command Line
  • Stepping Through Code
  • Creating a Breakpoint with set_trace()
  • Adding Breakpoints in pdb
  • Using the built-in breakpoint() Function
  • Getting Help

You should go and try to use what you have learned here in your own code. Adding intentional errors to your code and then running them through your debugger is a great way to learn how things work!

Copyright © 2024 Mouse Vs Python | Powered by Pythonlibrary