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 (n
ext, l
ist, c
ontinue, etc). For our particular use case however, the most interesting and useful command is b
reak:
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 b
reak 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!
-
For instance, when running on an intentionally immutable container ↩
Top comments (5)
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.
Yeah that was my usual approach. IIRC though, it didn't work with dependencies within a virtualenv.
However, that was some time ago so perhaps the issue has been fixed.
sh
orbash
would be more appropriate since it's cli here, not python-specific.Thanks for the suggestion!