开发者

Running a python debug session from a program, not from the console

开发者 https://www.devze.com 2023-04-12 01:15 出处:网络
I\'m writing a little python IDE, and I want to add simple debugging. I don\'t need all the features of winpdb.

I'm writing a little python IDE, and I want to add simple debugging. I don't need all the features of winpdb. How do I launch a python program (by file name) with a breakpoint set at a line number so that it runs until that line number and halts? Note that I don't want to do this from the command-line,开发者_C百科 and I don't want to edit the source (by inserting set_trace, for example). And I don't want it to stop at the first line so I have to run the debugger from there. I've tried all the obvious ways with pdb and bdb, but I must be missing something.


Pretty much the only viable way to do it (as far as I know) is to run Python as a subprocess from within your IDE. This avoids "pollution" from the current Python interpreter, which makes it fairly likely that the program will run in the same way as if you had started it independently. (If you have issues with this, check the subprocess environment.) In this manner, you can run a script in "debug mode" using

p = subprocess.Popen(args=[sys.executable, '-m', 'pdb', 'scriptname.py', 'arg1'],
                     stdin=subprocess.PIPE,
                     stdout=subprocess.PIPE,
                     stderr=subprocess.PIPE)

This will start up Python at the debugger prompt. You'll need to run some debugger commands to set breakpoints, which you can do like so:

o,e = p.communicate('break scriptname.py:lineno')

If this works, o should be the normal output of the Python interpreter after it sets a breakpoint, and e should be empty. I'd suggest you play around with this and add some checks in your code to ensure whether the breakpoints were properly set.

After that, you can start the program running with

p.communicate('continue')

At this point you'd probably want to hook the input, output, and error streams up to the console that you're embedding in your IDE. You would probably need to do this with an event loop, roughly like so:

while p.returncode is None:
    o,e = p.communicate(console.read())
    console.write(o)
    console.write(e)

You should consider that snippet to be effectively pseudocode, since depending on how exactly your console works, it'll probably take some tinkering to get it right.

If this seems excessively messy, you can probably simplify the process a bit using the features of Python's pdb and bdb modules (I'm guessing "Python debugger" and basic debugger" respectively). The best reference on how to do this is the source code of the pdb module itself. Basically, the way the responsibilities of the modules are split is that bdb handles "under the hood" debugger functionality, like setting breakpoints, or stopping and restarting execution; pdb is a wrapper around this that handles user interaction, i.e. reading commands and displaying output.

For your IDE-integrated debugger, it would make sense to adjust the behavior of the pdb module in two ways that I can think of:

  1. have it automatically set breakpoints during initialization, without you having to explicity send the textual commands to do so
  2. make it take input from and send output to your IDE's console

Just these two changes should be easy to implement by subclassing pdb.Pdb. You can create a subclass whose initializer takes a list of breakpoints as an additional argument:

class MyPDB(pdb.Pdb):
    def __init__(self, breakpoints, completekey='tab',
                 stdin=None, stdout=None, skip=None):
        pdb.Pdb.__init__(self, completekey, stdin, stdout, skip)
        self._breakpoints = breakpoints

The logical place to actually set up the breakpoints is just after the debugger reads its .pdbrc file, which occurs in the pdb.Pdb.setup method. To perform the actual setup, use the set_break method inherited from bdb.Bdb:

    def setInitialBreakpoints(self):
        _breakpoints = self._breakpoints
        self._breakpoints = None  # to avoid setting breaks twice
        for bp in _breakpoints:
            self.set_break(filename=bp.filename, line=bp.line,
                           temporary=bp.temporary, conditional=bp.conditional,
                           funcname=bp.funcname)

    def setup(self, f, t):
        pdb.Pdb.setup(self, f, t)
        self.setInitialBreakpoints()

This piece of code would work for each breakpoint being passed as e.g. a named tuple. You could also experiment with just constructing bdb.Breakpoint instances directly, but I'm not sure if that would work properly, since bdb.Bdb maintains its own information about breakpoints.

Next, you'll need to create a new main method for your module which runs it the same way pdb runs. To some extent, you can copy the main method from pdb (and the if __name__ == '__main__' statement of course), but you'll need to augment it with some way to pass in the information about your additional breakpoints. What I'd suggest is writing the breakpoints to a temporary file from your IDE, and passing the name of that file as a second argument:

tmpfilename = ...
# write breakpoint info
p = subprocess.Popen(args=[sys.executable, '-m', 'mypdb', tmpfilename, ...], ...)
# delete the temporary file

Then in mypdb.main(), you would add something like this:

def main():
    # code excerpted from pdb.main()
    ...
    del sys.argv[0]

    # add this
    bpfilename = sys.argv[0]
    with open(bpfilename) as f:
        # read breakpoint info
        breakpoints = ...
    del sys.argv[0]
    # back to excerpt from pdb.main()

    sys.path[0] = os.path.dirname(mainpyfile)

    pdb = Pdb(breakpoints) # modified

Now you can use your new debugger module just like you would use pdb, except that you don't have to explicitly send break commands before the process starts. This has the advantage that you can directly hook the standard input and output of the Python subprocess to your console, if it allows you to do that.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号