Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

python conditional debug breakpoint one-liner for versions before 3.7 PEP 553 that acts similarly to Perl's $DB::single=1

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.

like image 643
719016 Avatar asked Jun 14 '19 10:06

719016


People also ask

What does breakpoint () do in Python?

Python breakpoint() - Stop Debugging Python sys. breakpointhook() function uses environment variable PYTHONBREAKPOINT to configure the debugger. If unset, the default PDB debugger is used. If it's set to “0” then the function returns immediately and no code debugging is performed.

How do you set a breakpoint in Python pdb?

It's easy to set a breakpoint in Python code to i.e. inspect the contents of variables at a given line. Add import pdb; pdb. set_trace() at the corresponding line in the Python code and execute it. The execution will stop at the breakpoint.

How do you do a conditional breakpoint?

To set a conditional breakpointOn the Home tab, in the Breakpoints group, choose Set/Clear Condition. In the Debugger Breakpoint Condition window, enter a condition. On the Home tab, in the Breakpoints group, choose List. In the Debugger Breakpoint List window, enter a condition in the Condition column.

How do you debug a line by line in Python?

Starting Python Debugger To start debugging within the program just insert import pdb, pdb. set_trace() commands. Run your script normally, and execution will stop where we have introduced a breakpoint. So basically we are hard coding a breakpoint on a line below where we call set_trace().


2 Answers

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
like image 139
Arne Avatar answered Oct 16 '22 23:10

Arne


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
$ 
like image 36
Håkon Hægland Avatar answered Oct 17 '22 00:10

Håkon Hægland