简体   繁体   中英

How can I jump into my code in pdb in python rather than library code?

I'm writing some code; to do this I am letting my code fail and using python -m pdb to inspect what's going on. But when I do so the code starts a few layers down in library code and then I have to go up through the stack trace with the u key, which is distracting me a little from debugging.

Is it possible to have pdb post mortem go to the point in the stack trace in my code rather than some library?

Research so far

This blog post gives examples of a custom command in pdb. https://maurcz.github.io/posts/002-customizing-the-python-debugger/

So I had a go at doing this myself following this helpful post . An interesting exercise in understanding pdb.

import pdb

import sys
from types import FrameType

import pathlib
import getopt
import os

KNOWN_UNITS = {"KB", "MB", "BYTES"}


class TomsPdb(pdb.Pdb):

    Path = pathlib.Path

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.prompt = "[tomspdb] "

    @classmethod
    def frame_path(cls, frame) -> str:
        # WARNING. Pdb is weird. It delete all objects form the main namespace
        return cls.Path(frame.f_code.co_filename)

    @classmethod
    def in_my_code(cls, f: FrameType) -> bool:
        path = cls.frame_path(f)
        if any(p.name == "lib" for p in path.parents):
            return False
        return True

    def do_get_pdb(self, arg: str):
        del arg
        print("Set pself to pdb instance")
        self.curframe.f_globals["pself"] = self

    def do_top(self, arg: str):
        del arg
        print("Jump to the top of the stack. Equivalent to top -1")
        self._select_frame(0)

    do_T = do_top

    def do_bottom(self, arg: str):
        del arg
        print("Jump to the top of the bottom of the stack. Equivalent to down -1")
        self._select_frame(len(self.stack) - 1)

    do_B = do_bottom

    def do_get_frame(self, arg: str):
        del arg
        print("Putting current frame in frame variable")
        self.curframe.f_globals["frame"] = self.curframe

    def do_pretty_where(self, arg: str):
        del arg
        print("Print where we are in a pretty way")

        for frame_lineno in self.stack:
            self.pretty_print_stack_entry(frame_lineno)

    do_W = do_pretty_where

    def pretty_print_stack_entry(self, frame_lineno):
        frame: FrameType
        frame, lineno = frame_lineno
        if frame is self.curframe:
            prefix = "> "
        else:
            prefix = "  "

        filename = self.Path(frame.f_code.co_filename).name
        func_name = frame.f_code.co_name

        whose = "MINE" if self.in_my_code(frame) else "LIB"
        self.message(f"{prefix} {whose:10} {func_name:20} {filename:20} {lineno:20}")

    def do_my_code(self, arg: str):
        del arg
        "Jump out of library code into your code"
        mycode_level = max(
            i for i, (x, _) in enumerate(self.stack) if self.in_my_code(x)
        )
        self._select_frame(mycode_level)

    do_my = do_my_code


def main():
    # grumble at pdb for hard coding pdb type. Copy code across
    Restart = pdb.Restart
    import traceback
    import sys

    opts, args = getopt.getopt(sys.argv[1:], "mhc:", ["help", "command="])

    if not args:
        print(pdb._usage)
        sys.exit(2)

    commands = []
    run_as_module = False
    for opt, optarg in opts:
        if opt in ["-h", "--help"]:
            print(pdb._usage)
            sys.exit()
        elif opt in ["-c", "--command"]:
            commands.append(optarg)
        elif opt in ["-m"]:
            run_as_module = True

    mainpyfile = args[0]  # Get script filename
    print(mainpyfile)
    if not run_as_module and not os.path.exists(mainpyfile):
        print("Error:", mainpyfile, "does not exist")
        sys.exit(1)

    sys.argv[:] = args  # Hide "pdb.py" and pdb options from argument list

    print(args)

    if not run_as_module:
        mainpyfile = os.path.realpath(mainpyfile)
        # Replace pdb's dir with script's dir in front of module search path.
        sys.path[0] = os.path.dirname(mainpyfile)

    # Note on saving/restoring sys.argv: it's a good idea when sys.argv was
    # modified by the script being debugged. It's a bad idea when it was
    # changed by the user from the command line. There is a "restart" command
    # which allows explicit specification of command line arguments.
    p = TomsPdb()
    p.rcLines.extend(commands)
    while True:
        try:
            if run_as_module:
                p._runmodule(mainpyfile)
            else:
                p._runscript(mainpyfile)
            if p._user_requested_quit:
                break
            print("The program finished and will be restarted")
        except Restart:
            print("Restarting", mainpyfile, "with arguments:")
            print("\t" + " ".join(args))
        except SystemExit:
            # In most cases SystemExit does not warrant a post-mortem session.
            print("The program exited via sys.exit(). Exit status:", end=" ")
            print(sys.exc_info()[1])
        except SyntaxError:
            traceback.print_exc()
            sys.exit(1)
        except Exception:  # pylint: disable=broad-except
            traceback.print_exc()
            print("Uncaught exception. Entering post mortem debugging")
            print("Running 'cont' or 'step' will restart the program")
            t = sys.exc_info()[2]
            p.interaction(None, t)
            print(
                "Post mortem debugger finished. The "
                + mainpyfile
                + " will be restarted"
            )


if __name__ == "__main__":
    main()

This creates a command my_code (or my ) that jumps into your code. Here other people's code is defined by one of the parents being lib .

pdb seems relatively straightforward to interact with. The one slight complexity is python3 -m mypdb does some slightly odd things.

From the documentation (running python -m pdb ):

To let the script run up to a given line X in the debugged file, use -c 'until X' .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM