Imagine a typical work day as a developer. You write a function that's supposed to do something but either nothing happens or the wrong thing happens. Or, you are updating functions and BOOM! Nothing works like you expected. What can you do now? DEBUGGING!

Figuring out why code is not working or is not working properly is called debugging, and the process involves using your investigation skills. There are many ways to debug your code. Sometimes you only need to use one way to find the problem. Other times you may need to use different methods to pinpoint the problem and fix it.

Our hypothetical friend Millie experienced this today when writing the function below. She wanted the function to take two numbers and add them together, then print the total. This was her code:

def sample_func():
    a = int(input("First number: "))
    b = input("Second number: ")
    total = a + b
    return print(f"The total is {total}.")

sample_func()

The output in the console:

First number: 3
Second number: 5
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in sample_func
TypeError: unsupported operand type(s) for +: 'int' and 'str'

Now Millie needed to debug in order to fix her code. Debugging would help her to track down where the error is occurring and fix it.

Ways to debug Python code

There are several ways that you can debug your Python code. For example -

  • Reading through the code: Double-check spellings, typing, punctuation and indentations.

  • Use tools like black, autopep8, flake8 and mypy to find errors and fix the code.

  • You can use the built-in debugger in your code editor (check out debugging in Visual Studio Code).

    But this may not always work to find the bug, especially if you are using a framework like Django.

  • Reading through the error logs in the command line when you run the code (aka the traceback)

  • Adding print() statements around the problem code to see what it's doing.

  • The breakpoint() method...which is what we are going to talk about here.

    This method is called the Python Debugger (pdb). Before Python 3.7, this method is written as pdb.set_trace() but it essentially functions the same way.

Using the Python Debugger

When you debug, you may need to use several different methods of debugging the code in order to find out what's wrong and figure out how to fix it. The breakpoint() method can help you to find the cause for a bug that may not be found otherwise.

All you do is add the code breakpoint() on its own line wherever you want to start the debugging session. The method has different commands that you can use to run your code and identify the bug. To get a list of these commands, type h or help. That's it! Let's try an example to see how this works.

Let's take a look at Millie's function again.

def sample_func():
    a = int(input("First number: "))
    b = input("Second number: ")
    total = a + b
    return print(f"The total is {total}.")

sample_func()

To initiate the debugger, we need to insert breakpoint() at the point where we want to start the debugger. Let's start the debugger toward the beginning of the function, like this, and before each variable:

def sample_func():
    breakpoint()
    a = int(input("First number: "))
    b = input("Second number: ")
    total = a + b
    return print(f"The total is {total}.")

sample_func()

Our traceback had said that the problem is that strings and integers can't be added together. We need to find out what type each of our variables are to find which one is causing the problem, or if both are.

Then we run the code in the terminal. When the execution hits the breakpoint(), the debugger will start. You can see which commands are available with h or help, but let's take a look at the few commands that would be helpful to our hypothetical friend Millie as she debugs her code. Let's look at a few commands that Millie can use during her debugging:

The ll command

This command shows the current line and the surrounding lines.

> c:path/to/file/broken.py(3)sample_func()
-> a = int(input("First number: "))
(Pdb) ll
    1     def sample_func():
    2         breakpoint()
    3  ->     a = int(input("First number: "))
    4         b = input("Second number: ")
    5         total = a + b
    6         return print(f"The total is {total}.")

The n command

This command runs the line and goes onto the next line. So, now the debugger is working through the code and stopping on the next line, until we tell it to run that line. Going through code line by line is a good way to identify which line or lines contains the problem.

(Pdb) n
First number: 3
c:path/to/file/broken.py(3)sample_func()
-> b = input("Second number: ")

The display command

Use the display command to print the value of a variable. Let's make sure check on the variable a:

(Pdb) display a
display a: 3

So, Millie runs the n command to run the line for input b variable, the the display command to check it.

(Pdb) n
Second number: 4
(Pdb) display b
display b: '4'

Compare how a and b are both displayed. Something is different.

(Pdb) display a
display a: 3
(Pdb) display b
display b: '4'

The debug command

The display command gave some insight into the variables, but the debug command can show us some more information.

(Pdb) debug
ENTERING RECURSIVE DEBUGGER
> <string>(1)<module>()

What does this mean? The <string>(1)<module>() says that item at index 1 is a string in our module (function). The item at index 1 in our module would be the variable b (because a would be at index 0 within the module).

Now we know which variable is causing the problem! Variable b is being saved as a string and not as an integer! Millie fixes the code and runs the program again, removing the breakpoints.

def sample_func():
    a = int(input("First number: "))
    b = int(input("Second number: "))
    total = a + b
    return print(f"The total is {total}.")

sample_func()

End the current debugger session with exit and run the function again. Now it should work because the problem was fixed fixed. Variable b is now being converted into an integer instead of rendered as a string.

This was an overly simplified example, of course, but breakpoint() can be a great tool for you to use when debugging your code. Use it in combination with other methods to narrow down the problem if you can't fully identify the issue otherwise.