如何使 pdb 识别出运行之间的源已更改?

发布于 2024-07-16 02:07:49 字数 310 浏览 4 评论 0原文

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

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

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

From what I can tell, pdb does not recognize when the source code has changed between "runs". That is, if I'm debugging, notice a bug, fix that bug, and rerun the program in pdb (i.e. without exiting pdb), pdb will not recompile the code. I'll still be debugging the old version of the code, even if pdb lists the new source code.

So, does pdb not update the compiled code as the source changes? If not, is there a way to make it do so? I'd like to be able to stay in a single pdb session in order to keep my breakpoints and such.

FWIW, gdb will notice when the program it's debugging changes underneath it, though only on a restart of that program. This is the behavior I'm trying to replicate in pdb.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

星星的轨迹 2024-07-23 02:07:49

以下迷你模块可能会有所帮助。 如果将其导入到 pdb 会话中,则可以使用:

pdb> pdbs.r()

随时强制重新加载除 ma​​in 之外的所有非系统模块。 该代码会跳过该过程,因为它会引发 ImportError('Cannot re-init inside 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)

The following mini-module may help. If you import it in your pdb session, then you can use:

pdb> pdbs.r()

at any time to force-reload all non-system modules except main. The code skips that because it throws an ImportError('Cannot re-init internal module main') exception.

# 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)
南笙 2024-07-23 02:07:49

基于 @pourhaus 的回答(2014 年),此配方增强了 pdb++reload 命令的调试器(预计可以在 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

Based on @pourhaus answer (from 2014), this recipe augments the pdb++ debugger with a reload command (expected to work on both Linux & Windows, on any Python installation).

TIP: the new reload command accepts an optional list of module-prefixes to reload (and to exclude), not to break already loaded globals when resuming debugging.

Just insert the following Python-3.6 code into your ~/.pdbrc.py file:

## 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
指尖凝香 2024-07-23 02:07:49

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

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

(抱歉,忽略这个答案的第一个版本;现在还很早,我没有足够仔细地阅读你的问题。)

What do you mean by "rerun the program in pdb?" If you've imported a module, Python won't reread it unless you explicitly ask to do so, i.e. with reload(module). However, reload is far from bulletproof (see xreload for another strategy).

There are plenty of pitfalls in Python code reloading. 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.

(Sorry, ignore the first version of this answer; it's early and I didn't read your question carefully enough.)

对你的占有欲 2024-07-23 02:07:49

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

(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".

I decided to comment some lines in my input script, and after

(Pdb) run

I got pdb to recognize that change. The bad thing: it runs the script from the beginning. The good things below.

(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".
风启觞 2024-07-23 02:07:49

我是 Python 新手,但在阅读其他答案后,我对现在存在的问题有了一个想法。 如果您“注意到一个错误,修复该错误,然后在 pdb 中重新运行该程序(即不退出 pdb)”,那么您希望 pdb 继续使用的状态(断点、命令等)很可能不会出现。 t 也可以,特别是因为行号可能已经改变,并且与断点及其使用的条件相关的任何逻辑都可能被破坏。

对于您不想重复输入的有用调试命令,文档说“如果文件 .pdbrc 存在于用户的主目录或当前目录中,则使用 'utf-8' 编码读取并执行它,就像它一样已在调试器提示符下输入。”

这里要解决的最后一个大问题是如何在 pdb 中保留有用的 commands 命令,因为 alias 没有指定如何为多行 pdb 命令添加别名(其中 < code>commands 是我唯一知道的)。

I'm new to Python, but I had a thought about the question as it exists now, after reading the other answers. If you "notice a bug, fix that bug, and rerun the program in pdb (i.e. without exiting pdb)," then it is likely that the state you'd like pdb to keep using (breakpoints, commands, etc.) won't serve as well, specifically because line numbers may have changed, and any logic related to breakpoints and the conditions they use may be broken.

For useful debugging commands that you don't want to type repeatedly, the docs say "If a file .pdbrc exists in the user’s home directory or in the current directory, it is read with 'utf-8' encoding and executed as if it had been typed at the debugger prompt."

The last big problem to solve here is how to preserve a useful commands command in pdb, since alias doesn't specify how to alias multi-line pdb commands (of which commands is the only one I know).

七秒鱼° 2024-07-23 02:07:49

可能不适用于更复杂的程序,但对于使用 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')

May not work for more complex programs, but for a simple example using importlib.reload() using Python v3.5.3:

[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')
一个人的旅程 2024-07-23 02:07:49

ipdb %autoreload 扩展

6.2.0 文档 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

ipdb %autoreload extension

6.2.0 docs document 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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文