Contents | Previous (8.2 Logging) | Next (9 Packages)

8.3 Debugging

Debugging Tips

So, your program has crashed…

bash % python3 blah.py
Traceback (most recent call last):
  File "blah.py", line 13, in ?
    foo()
  File "blah.py", line 10, in foo
    bar()
  File "blah.py", line 7, in bar
    spam()
  File "blah.py", 4, in spam
    line x.append(3)
AttributeError: 'int' object has no attribute 'append'

Now what?!

Reading Tracebacks

The last line is the specific cause of the crash.

bash % python3 blah.py
Traceback (most recent call last):
  File "blah.py", line 13, in ?
    foo()
  File "blah.py", line 10, in foo
    bar()
  File "blah.py", line 7, in bar
    spam()
  File "blah.py", 4, in spam
    line x.append(3)
# Cause of the crash
AttributeError: 'int' object has no attribute 'append'

However, it’s not always easy to read or understand.

PRO TIP: Paste the whole traceback into Google.

Using the REPL

Use the option -i to keep Python alive when executing a script.

bash % python3 -i blah.py
Traceback (most recent call last):
  File "blah.py", line 13, in ?
    foo()
  File "blah.py", line 10, in foo
    bar()
  File "blah.py", line 7, in bar
    spam()
  File "blah.py", 4, in spam
    line x.append(3)
AttributeError: 'int' object has no attribute 'append'
>>>

It preserves the interpreter state. That means that you can go poking around after the crash. Checking variable values and other state.

Debugging with Print

print() debugging is quite common.

Tip: Make sure you use repr()

def spam(x):
    print('DEBUG:', repr(x))
    ...

repr() shows you an accurate representation of a value. Not the nice printing output.

>>> from decimal import Decimal
>>> x = Decimal('3.4')
# NO `repr`
>>> print(x)
3.4
# WITH `repr`
>>> print(repr(x))
Decimal('3.4')
>>>

The Python Debugger

You can manually launch the debugger inside a program.

def some_function():
    ...
    breakpoint()      # Enter the debugger (Python 3.7+)
    ...

This starts the debugger at the breakpoint() call.

In earlier Python versions, you did this. You’ll sometimes see this mentioned in other debugging guides.

import pdb
...
pdb.set_trace()       # Instead of `breakpoint()`
...

Run under debugger

You can also run an entire program under debugger.

bash % python3 -m pdb someprogram.py

It will automatically enter the debugger before the first statement. Allowing you to set breakpoints and change the configuration.

Common debugger commands:

(Pdb) help            # Get help
(Pdb) w(here)         # Print stack trace
(Pdb) d(own)          # Move down one stack level
(Pdb) u(p)            # Move up one stack level
(Pdb) b(reak) loc     # Set a breakpoint
(Pdb) s(tep)          # Execute one instruction
(Pdb) c(ontinue)      # Continue execution
(Pdb) l(ist)          # List source code
(Pdb) a(rgs)          # Print args of current function
(Pdb) !statement      # Execute statement

For breakpoints location is one of the following.

(Pdb) b 45            # Line 45 in current file
(Pdb) b file.py:45    # Line 45 in file.py
(Pdb) b foo           # Function foo() in current file
(Pdb) b module.foo    # Function foo() in a module

Exercises

Exercise 8.4: Bugs? What Bugs?

It runs. Ship it!

Contents | Previous (8.2 Logging) | Next (9 Packages)