DEV Community

Cover image for The lesser known (but incredibly useful) way of settings breakpoints in Python
Demian Brecht
Demian Brecht

Posted on • Edited on

The lesser known (but incredibly useful) way of settings breakpoints in Python

Everyone knows that in order to set a breakpoint in Python you need to modify your source code with the somewhat unintuitive

import pdb; pdb.set_trace() # or if >= 3.7 breakpoint() 
Enter fullscreen mode Exit fullscreen mode

Right?

But

Did you know that there's a way to set breakpoints that doesn't involve modifying your code?

"What's this silliness you speak of?" you say?

There may be times when you have to jump through ridiculous hoops in order to modify code1 and the more common method of setting breakpoints in Python isn't worthwhile. For those cases, you have the ability to run your code interactively through a pdb shell:

python -m pdb <script>.py 
Enter fullscreen mode Exit fullscreen mode

That's right, pdb can be run as an executable module! Neat-o, right? Now you'll be dropped into the pdb shell, from which you can run your usual pdb commands (next, list, continue, etc). For our particular use case however, the most interesting and useful command is break:

 b(reak) [ ([filename:]lineno | function) [, condition] ] Without argument, list all breaks. With a line number argument, set a break at this line in the current file. With a function name, set a break at the first executable line of that function. If a second argument is present, it is a string specifying an expression which must evaluate to true before the breakpoint is honored. The line number may be prefixed with a filename and a colon, to specify a breakpoint in another file (probably one that hasn't been loaded yet). The file is searched for on sys.path; the .py suffix may be omitted. 
Enter fullscreen mode Exit fullscreen mode

The break command is incredibly flexible. You can specify breakpoint locations by line number (prefixing it with a filename is optional), by function and even slap on a condition.

As an example, consider the following example.py:

import requests def foo(): print('foo') def bar(value='bar'): print(value) def get(): print(requests.get('https://example.com/')) if __name__ == '__main__': foo() bar() bar(value='foo') get() 
Enter fullscreen mode Exit fullscreen mode

Setting a breakpoint with (filename):lineno

This can be useful when we know the exact file and line we want to break execution at. This can be really useful if you want to break into the debugger shell at import time rather than execution (debugging circular references, etc).

$ python -m pdb example.py > /home/dbrecht/src/p/playground/example.py(1)<module>() -> import requests (Pdb) b example.py:5 Breakpoint 1 at /home/dbrecht/src/p/playground/example.py:5 (Pdb) c > /home/dbrecht/src/p/playground/example.py(5)foo() -> print('foo') 
Enter fullscreen mode Exit fullscreen mode

Setting a breakpoint by function

This is generally the easiest way to set breakpoints, especially when dealing with dependencies. For this example, let's say we already know that requests.get eventually calls into requests.request and we want to stop at that point rather than having to step through preamble code in requests.get:

$ python -m pdb example.py > /home/dbrecht/src/p/playground/example.py(1)<module>() -> import requests (Pdb) import requests (Pdb) b requests.request Breakpoint 1 at /usr/lib/python2.7/dist-packages/requests/api.py:16 (Pdb) c foo bar > /usr/lib/python2.7/dist-packages/requests/api.py(57)request() -> with sessions.Session() as session: (Pdb) w /usr/lib/python2.7/bdb.py(400)run() -> exec cmd in globals, locals <string>(1)<module>() /home/dbrecht/src/p/playground/example.py(19)<module>() -> get() /home/dbrecht/src/p/playground/example.py(13)get() -> print(requests.get('https://example.com/')) /usr/lib/python2.7/dist-packages/requests/api.py(72)get() -> return request('get', url, params=params, **kwargs) > /usr/lib/python2.7/dist-packages/requests/api.py(57)request() -> with sessions.Session() as session: 
Enter fullscreen mode Exit fullscreen mode

It's worthwhile to note that it's possible to import and set breakpoints in dependencies even before your code has done any importing itself.

Setting a breakpoint with a condition

Let's say we want to break in the example file's bar method, but only when the parameter value is equal to "foo". This can be incredibly useful when we don't want to break into program execution on every iteration of a function or method:

$ python -m pdb example.py > /home/dbrecht/src/p/playground/example.py(1)<module>() -> import requests (Pdb) b bar, value=="foo" Breakpoint 1 at /home/dbrecht/src/p/playground/example.py:8 (Pdb) c foo bar > /home/dbrecht/src/p/playground/example.py(9)bar() -> print(value) (Pdb) value 'foo' 
Enter fullscreen mode Exit fullscreen mode

Setting a temporary breakpoint

tbreak is another useful way to set breakpoints if we only want to break into execution once. The breakpoint is then automatically discarded. In this example, we only want to cause a break at the first call into bar:

$ python -m pdb example.py > /home/dbrecht/src/p/playground/example.py(1)<module>() -> import requests (Pdb) tbreak bar Breakpoint 1 at /home/dbrecht/src/p/playground/example.py:8 (Pdb) c foo Deleted breakpoint 1 > /home/dbrecht/src/p/playground/example.py(9)bar() -> print(value) (Pdb) value 'bar' (Pdb) c bar foo <Response [200]> 
Enter fullscreen mode Exit fullscreen mode

These methods have definitely been useful to me in the past. If you didn't already know about them, hopefully they will be a new tool to add to your debugging arsenal!


  1. For instance, when running on an intentionally immutable container 

Top comments (5)

Collapse
 
iceorfiresite profile image
Ice or Fire

If you're using VS Code for your Python IDE, you can run the debugger inside the IDE and just click the line number to set breakpoints.

Collapse
 
demianbrecht profile image
Demian Brecht

Yeah that was my usual approach. IIRC though, it didn't work with dependencies within a virtualenv.

Collapse
 
demianbrecht profile image
Demian Brecht

However, that was some time ago so perhaps the issue has been fixed.

Collapse
 
mburszley profile image
Maximilian Burszley

sh or bash would be more appropriate since it's cli here, not python-specific.

Collapse
 
demianbrecht profile image
Demian Brecht

Thanks for the suggestion!