【问题标题】:How to make pdb recognize that the source has changed between runs?如何让 pdb 识别出源在运行之间发生了变化?
【发布时间】:2009-04-07 10:09:09
【问题描述】:

据我所知,pdb 无法识别源代码在“运行”之间发生变化的时间。也就是说,如果我在调试,发现一个 bug,修复那个 bug,然后在 pdb 中重新运行程序(即不退出 pdb),pdb 将不会重新编译代码。即使 pdb 列出了新的源代码,我仍然会调试旧版本的代码。

那么,pdb 不会随着源代码的变化而更新编译后的代码吗?如果没有,有没有办法让它这样做?我希望能够留在单个 pdb 会话中以保留断点等。

FWIW,gdb 会在它正在调试的程序在它下面发生更改时注意到,尽管只有在该程序重新启动时才会注意到。这是我试图在 pdb 中复制的行为。

【问题讨论】:

    标签: python debugging pdb


    【解决方案1】:

    以下迷你模块可能会有所帮助。如果你在你的 pdb 会话中导入它,那么你可以使用:

    pdb> pdbs.r()
    

    随时强制重新加载除 ma​​in 之外的所有非系统模块。代码跳过了这一点,因为它引发了 ImportError('Cannot re-init internal module ma​​in') 异常。

    # pdbs.py - PDB support
    
    from __future__ import print_function
    
    def r():
        """Reload all non-system modules, to reload stuff on pbd restart. """
        import importlib
        import sys
    
        # This is likely to be OS-specific
        SYS_PREFIX = '/usr/lib'
    
        for k, v in list(sys.modules.items()):
            if (
                k == "__main__" or
                k.startswith("pdb") or
                not getattr(v, "__file__", None)
                or v.__file__.startswith(SYS_PREFIX)
            ):
                continue
            print("reloading %s [%s]" % (k, v.__file__), file=sys.stderr)
            importlib.reload(v)
    

    【讨论】:

    • 当我运行 pdb.r() 时,我得到:*** AttributeError: 'dict' object has no attribute 'iteritems'
    • 我很抱歉 - .iteritems() 与 Python 3.x 不兼容。我已经修改了代码,使它应该可以工作。
    【解决方案2】:

    “在 pdb 中重新运行程序”是什么意思?如果你导入了一个模块,Python 不会重新读取它,除非你明确要求这样做,即使用reload(module)。然而,reload 远非万无一失(另一种策略请参见xreload)。

    Python 代码重载有很多陷阱。例如,为了更稳健地解决您的问题,您可以使用一个类将 pdb 包装起来,该类将您的断点信息记录到磁盘上的文件中,并在命令中播放它们。

    (对不起,忽略此答案的第一个版本;现在还早,我没有仔细阅读您的问题。)

    【讨论】:

    • 我所说的“重新运行”在概念上是指从命令行重新运行 python 程序。 pdb 清楚地理解我的程序退出的概念,所以我想知道,当它第二次运行我的程序时,我是否可以让它在必要时重新编译源代码。
    • 您可以尝试在 pdb 内部使用reload,然后再重新运行它,但同样,根据您的程序结构,它可能不可靠。 (FWIW,我认为这是 Python 作为一门语言的最大失败。来自 Smalltalk 和 Lisp 等环境,这令人沮丧。)
    • @NicholasRiley 我想将 xreload 作为命令合并到 python trepan 调试器中(trepan3k pypi.python.org/pypi/trepan3k 和 trepan2 pypi.python.org/pypi/trepan2)。这些是 GPL3。这个可以吗?你是作者吗?
    • @NicholasRiley,你能举一个To more robustly solve your problem, you could wrap pdb with a class that records your breakpoint info to a file on disk, for example, and plays them back on command.的例子吗?我在调试时经常遇到这个问题,并试图看看是否有一种强大的重新加载模块的方法..
    • 老实说,自从我编写上述内容以来已经 9 年了,我现在使用多个不错的 GUI Python 调试器,而不是尝试破解 pdb 以保持断点(如果你在任何情况下都会中断)编辑了您的文件并更改了行号)。如果您确实想在 pdb 中保留断点,请参阅 stackoverflow.com/questions/28841520/save-breakpoints-to-file 示例。
    【解决方案3】:

    基于@pourhaus 的回答(从 2014 年开始),此配方使用 reload 命令扩充了 pdb++ 调试器(预计可在 Linux 和 Windows 上工作,在任何 Python 安装上)。

    提示:新的reload 命令接受一个可选的模块前缀列表以重新加载(以及排除),而不是在恢复调试时破坏已加载的全局变量 .

    只需将以下 Python-3.6 代码插入到您的 ~/.pdbrc.py 文件中:

    ## Augment `pdb++` with a `reload` command
    #
    #  See https://stackoverflow.com/questions/724924/how-to-make-pdb-recognize-that-the-source-has-changed-between-runs/64194585#64194585
    
    from pdb import Pdb
    
    
    def _pdb_reload(pdb, modules):
        """
        Reload all non system/__main__ modules, without restarting debugger.
    
        SYNTAX:
            reload [<reload-module>, ...] [-x [<exclude-module>, ...]]
    
        * a dot(`.`) matches current frame's module `__name__`;
        * given modules are matched by prefix;
        * any <exclude-modules> are applied over any <reload-modules>.
    
        EXAMPLES:
            (Pdb++) reload                  # reload everything (brittle!)
            (Pdb++) reload  myapp.utils     # reload just `myapp.utils`
            (Pdb++) reload  myapp  -x .     # reload `myapp` BUT current module
    
        """
        import importlib
        import sys
    
        ## Derive sys-lib path prefix.
        #
        SYS_PREFIX = importlib.__file__
        SYS_PREFIX = SYS_PREFIX[: SYS_PREFIX.index("importlib")]
    
        ## Parse args to decide prefixes to Include/Exclude.
        #
        has_excludes = False
        to_include = set()
        # Default prefixes to Exclude, or `pdb++` will break.
        to_exclude = {"__main__", "pdb", "fancycompleter", "pygments", "pyrepl"}
        for m in modules.split():
            if m == "-x":
                has_excludes = True
                continue
    
            if m == ".":
                m = pdb._getval("__name__")
    
            if has_excludes:
                to_exclude.add(m)
            else:
                to_include.add(m)
    
        to_reload = [
            (k, v)
            for k, v in sys.modules.items()
            if (not to_include or any(k.startswith(i) for i in to_include))
            and not any(k.startswith(i) for i in to_exclude)
            and getattr(v, "__file__", None)
            and not v.__file__.startswith(SYS_PREFIX)
        ]
        print(
            f"PDB-reloading {len(to_reload)} modules:",
            *[f"  +--{k:28s}:{getattr(v, '__file__', '')}" for k, v in to_reload],
            sep="\n",
            file=sys.stderr,
        )
    
        for k, v in to_reload:
            try:
                importlib.reload(v)
            except Exception as ex:
                print(
                    f"Failed to PDB-reload module: {k} ({v.__file__}) due to: {ex!r}",
                    file=sys.stderr,
                )
    
    
    Pdb.do_reload = _pdb_reload
    

    【讨论】:

    • 得到错误TypeError: super(type, obj): obj must be an instance or subtype of type。我之前似乎在 IPython 中遇到过这个错误。
    【解决方案4】:

    我决定在我的输入脚本中注释一些行,然后

    (Pdb) run
    

    我让 pdb 认识到这一变化。坏事:它从一开始就运行脚本。下面的好东西。

    (Pdb) help run
    run [args...]
            Restart the debugged python program. If a string is supplied
            it is split with "shlex", and the result is used as the new
            sys.argv.  History, breakpoints, actions and debugger options
            are preserved.  "restart" is an alias for "run".
    

    【讨论】:

    • 令人惊讶的是这有效。从编译语言背景来看,这有点违反直觉......
    【解决方案5】:

    可能不适用于更复杂的程序,但对于使用 Python v3.5.3 使用 importlib.reload() 的简单示例:

    [user@machine ~] cat test.py
    print('Test Message')
    
    #
    # start and run with debugger
    #
    [user@machine ~] python3 -m pdb test.py
    > /home/user/test.py(1)<module>()
    -> print('Test Message')
    (Pdb) c
    Test Message
    The program finished and will be restarted
    > /home/user/test.py(1)<module>()
    -> print('Test Message')
    
    #
    # in another terminal, change test.py to say "Changed Test Message"
    #
    
    #
    # back in PDB:
    #
    (Pdb) import importlib; import test; importlib.reload(test)
    Changed Test Message
    <module 'test' from '/home/user/test.py'>
    (Pdb) c
    Test Message
    The program finished and will be restarted
    > /home/user/test.py(1)<module>()
    -> print('Changed Test Message')
    (Pdb) c
    Changed Test Message
    The program finished and will be restarted
    > /home/user/test.py(1)<module>()
    -> print('Changed Test Message')
    

    【讨论】:

      【解决方案6】:

      ipdb %autoreload 扩展

      6.2.0 docs 文档http://ipython.readthedocs.io/en/stable/config/extensions/autoreload.html#module-IPython.extensions.autoreload

      In [1]: %load_ext autoreload
      
      In [2]: %autoreload 2
      
      In [3]: from foo import some_function
      
      In [4]: some_function()
      Out[4]: 42
      
      In [5]: # open foo.py in an editor and change some_function to return 43
      
      In [6]: some_function()
      Out[6]: 43
      

      【讨论】:

      • 是的..这不是一个正确的答案。如果您已经在调试器中,%autoreload 扩展实际上不会重新加载您的模块。仅当您在 ipython 中工作时,重新加载才有效。如果你重新执行代码,在修改ipython中的代码后,它会重新加载代码。否则不行。
      猜你喜欢
      • 2016-02-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-11-26
      • 1970-01-01
      相关资源
      最近更新 更多