DEV Community

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

Posted on • Updated 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() 

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

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.

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()

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')

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:

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'

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]>

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!