Question

How to ignore a specific breakpoint interactively?

Consider this script:

print("before loop")
for i in range(100):
    breakpoint()
print("after loop")
breakpoint()
print("exit")

Short of pressing "c" one hundred times, how can you get past the breakpoint within the loop at L3 and proceed to L5?

I've tried the ignore command but couldn't work it out:

$ python3 example.py
before loop
> /tmp/example.py(2)<module>()
-> for i in range(100):
(Pdb) ignore 0
*** Breakpoint 0 already deleted
(Pdb) c
> /tmp/example.py(2)<module>()
-> for i in range(100):
(Pdb) ignore 0
*** Breakpoint 0 already deleted
(Pdb) c
> /tmp/example.py(2)<module>()
-> for i in range(100):

I want to execute the remainder of the loop, without tripping again the breakpoint on L3, then print "after loop" and break before printing "exit", remaining in the debugger. The answer must not require exiting the debugger and re-entering the runtime, or modifying the source code.

 4  76  4
1 Jan 1970

Solution

 2

You can use the PYTHONBREAKPOINT environment variable to call a custom breakpoint handler that you define in a separate file.

For example:

$ export PYTHONBREAKPOINT=mybreak.mybreak
# mybreak/__init__.py

import pdb

_counter = 0

def mybreak(*args, **kwargs):
    global _counter
    if _counter >= 100:
        # default behavior
        pdb.set_trace(*args, **kwargs)
    else:
        # skip dropping into pdb while inside the range(100) loop
        pass
    _counter += 1

You might have to get a little tricky if there are other invocations of breakpoint() in the file but it would just be a matter of tracking what the counter value would be when you want to skip over the breakpoint.

An alternate implementation of the custom handler, as suggested in nneonneo's answer:

# mybreak/__init__.py

import inspect
import pdb

def mybreak():
    caller = inspect.stack()[1]
    if caller.filename.endswith("/foo.py") and caller.lineno == 2:
        # skip this breakpoint
        return
    pdb.set_trace(*args, **kwargs)
2024-07-09
Woodford

Solution

 1

breakpoint is just a regular Python function, so for an ugly but functional solution, you can overwrite breakpoint temporarily with a new function that selectively skips breakpoints.

Suppose foo.py contains the following:

for i in range(100):
    breakpoint()

breakpoint()
print("hi")

Run foo.py, and it'll immediately break at the first breakpoint, where continuing repeatedly keeps going around the loop:

> /code/foo.py(1)<module>()
-> for i in range(100):
(Pdb) c
> /code/foo.py(1)<module>()
-> for i in range(100):
(Pdb) c
> /code/foo.py(1)<module>()
-> for i in range(100):

Put the following in a temporary module (e.g. tmp_break_3zfh.py):

import inspect
import builtins

def breakpoint():
    caller = inspect.stack()[1]
    if caller.filename.endswith("/foo.py") and caller.lineno == 2:
        return
    builtins.breakpoint()

Then import it from the pdb console to overwrite the real breakpoint function for that module alone. When we continue, then step (to get out of the wrapper breakpoint and into the caller), we can see that we're stopped after the loop:

(Pdb) from tmp_break_3zfh import breakpoint
(Pdb) c
--Return--
> /code/tmp_break_3zfh.py(8)breakpoint()->None
-> builtins.breakpoint()
(Pdb) s
--Return--
> /code/foo.py(5)<module>()
-> print("hi")
2024-07-09
nneonneo

Solution

 1

To refine the answer by nneonneo to clearly satisfy "without restarting the interpreter or modifying the source code" requirement, you don't necessarily need to provide hacks inside breakpoint.

You can temporarily disable the breakpoint builtin and restore it later. To restore, just add a pdb breakpoint right after the loop and reassign it back.

Instead of storing original breakpoint in a variable, you can use __import__('builtins').breakpoint - that will refer to original function as well (and is better when your loop is not actually in the body of the script, see below).

# python /tmp/q.py
before loop
> /tmp/q.py(2)<module>()
-> for i in range(100):
(Pdb) _my_old_breakpoint = breakpoint
(Pdb) breakpoint = lambda: None
(Pdb) break 4
Breakpoint 1 at /tmp/q.py:4
(Pdb) c
> /tmp/q.py(4)<module>()
-> print("after loop")
(Pdb) breakpoint = _my_old_breakpoint
(Pdb) c
after loop
> /tmp/q.py(6)<module>()
-> print("exit")
(Pdb) c
exit

As far as I know, there is no way to enable/disable breakpoint calls via pdb, it only manages "custom" breakpoints set from within pdb session.

If that loop is not in global scope, you'll need to modify builtins instead:

cat > /tmp/q.py <<EOF
def main():
    print("before loop")
    for i in range(100):
        breakpoint()
    print("after loop")
    breakpoint()
    print("exit")

main()
EOF

# python /tmp/q.py
before loop
> /tmp/q.py(3)main()
-> for i in range(100):
(Pdb) import builtins
(Pdb) builtins._my_old_breakpoint=breakpoint
(Pdb) builtins.breakpoint = lambda: None
(Pdb) break 5
Breakpoint 1 at /tmp/q.py:5
(Pdb) c
> /tmp/q.py(5)main()
-> print("after loop")
(Pdb) builtins.breakpoint = builtins._my_old_breakpoint
(Pdb) c
after loop
> /tmp/q.py(7)main()
-> print("exit")
(Pdb) c
exit
2024-07-09
STerliakov