In python versions before PEP 553 breakpoint() utility, what is the recommended way to add (ideally a one-liner) code to have a breakpoint that can be ignored upon a condition (e.g. a global debug flag or args.debug flag).

In Perl, I am used to use $DB::single=1;1; single-lines, which I know I can safely leave in the code and won't affect the normal running of perl code.pl unless explicitly calling perl -d code.pl. E.g.:

my $a = 1;
$DB::single=1;1; # breakpoint line
my $b = 2;
print "$a $b\n";

If I run this code as: perl code.pl, it will run to completion. If I run this code with: perl -d code.pl, the pdb will stop at the breakpoint line (not before the next line with a my $b = 2; statement) because it contains a 1; statement after the $DB::single=1; statement;

Similarly, if I write:

my $debug = 1;
my $a = 1;
$DB::single=$debug;1; # first breakpoint line
my $b = 2;
$DB::single=$debug;1; # second breakpoint line
print "$a $b\n";
# [...] Lots more code sprinkled with more of these
$DB::single=$debug;1; # n'th breakpoint line

I can then execute perl -d code.pl, which will stop in the first breakpoint line, then in the pdb session, once I am happy that it does not need stopping anywhere else, then execute: $debug = 0, then pdb continue c, which will make it not stop at the second or other similar breakpoint lines in the code.

How can I achieve the same, ideally in single-line statements, in python (2.x and 3.x before PEP 553)?

I am aware of PEP 553 and apart from the hassle of having to explicitly set PYTHONBREAKPOINT=0 python3.7 code.py or comment out the breakpoint() lines, it is a solution to the question here.

I thought of options like:

import pdb; pdb.set_trace()
dummy=None;

The statement underneath pdb.set_trace() is so that I can achieve the same as the 1; in the same line after $DB::single=1; in Perl, which is to have the debugger stop where I placed the breakpoint, rather than the next statement. This is so that if there are large chunks of commented code or documentation in-between, the debugger does not jump to the next statement far away from the breakpoint.

Or with conditionals like:

if args.debug or debug:
    import pdb; pdb.set_trace()
    _debug=False; #args.debug=False

So that if I am done debugging for a script, I can set args.debug=False or debug=False and not have to touch all these breakpoints in the code.

2

There are 2 best solutions below

4
On

Here is a simple way using a .pdbrc file in the current directory:

t.py

def my_trace():
    global debug
    if debug:
        import pdb; pdb.set_trace()

debug = True
a= 1
my_trace()
b = 2
c = 3
my_trace()
d = 4

.pdbrc:

r

Example session:

$ python t.py
--Return--
> [...]/t.py(12)<module>()
-> b = 2
(Pdb) p a
1
(Pdb) p b
*** NameError: name 'b' is not defined
(Pdb) !debug=False
(Pdb) c
$ 
1
On

Setting a conditional breakpoint

Same as perl, python can be run with -d to set a debug flag:

$ python --help
[...]
-d     : debug output from parser; also PYTHONDEBUG=x
[...]

You can check its state during runtime via sys.flags :

$ python -d
Python 2.7.15+ (default, Nov 27 2018, 23:36:35) 
[GCC 7.3.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.flags
sys.flags(debug=1, py3k_warning=0, division_warning=0, ...)
#         ^ there it is, right at the front

Which allows for the following one-liner to enable debugging:

import pdb, sys; pdb.set_trace() if sys.flags[0] else None

Disabling conditional breakpoints from the debugger

Regarding this part

[...] once I am happy that it does not need stopping anywhere else, then execute [something] which will make it not stop at the second or other similar breakpoint lines in the code.

it gets a little trickier though, since python doesn't allows mutation of the flags structure, or even creating an instance of it:

>>> import sys
>>> sys.flags.debug = 0                 # no mutating ...
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: readonly attribute
>>> type(sys.flags)()                   # ... and no instanciating
Traceback (most recent call last):
  File "<input>", line 1, in <module>
TypeError: cannot create 'sys.flags' instances

But as far as I have tested, unless you run python with other flags, the following works to deactivate subsequent traces without altering your program's other behavior:

import sys; sys.flags = [0]*len(sys.flags)  # even fits onto a single line

For a slightly more robust monkey patch that you should use in case the former one leads to strange bugs, you'll need to have something like this:

def untrace():
  """Call this function in the pdb session to skip debug-only set_trace() calls"""
  import re
  import sys
  from collections import namedtuple  # has the same interface as struct sequence
  sys.flags = namedtuple(
    'sys_flags', 
    [m.group() for m in re.finditer(r'\w{2,}', repr(sys.flags)[10:-1])]
  )(0, *sys.flags[1:])

While this statement can be put on a single line, it's probably a little too much. You can either paste this function into the .py file where you plan to use it, or have some kind of utils.py from which you import it during debugging, after which a c(ontinue) should again run the rest of the program:

(Pdb) import utils; utils.untrace()
(Pdb) c