使用IPython逐步调试

203
根据我所了解的,有两种方法可以在Python中调试代码:
1. 使用传统的调试器,例如`pdb`或`ipdb`。这支持命令,例如`c`(继续),`n`(跨过),`s`(进入)等,但您无法直接访问IPython shell,这对于对象检查非常有用。
2. 通过在代码中embedding一个IPython shell来使用 IPython 。您可以执行from IPython import embed,然后在代码中使用embed()。当程序/脚本遇到embed()语句时,您将进入IPython shell。这允许完全检查对象并使用所有IPython好东西测试Python代码。但是,使用embed()时,您无法再使用方便的键盘快捷键逐步地浏览代码。

有没有办法将两者的优点结合起来呢?即:

  1. 能够使用方便的pdb/ipdb键盘快捷方式逐步执行您的代码。
  2. 在任何这样的步骤(例如,在给定语句上),都可以访问全功能的IPython shell。

IPython调试与MATLAB类似:

在MATLAB中可以找到此类“增强型调试”的示例,其中用户始终可以完全访问MATLAB引擎/ shell,并且仍然可以逐步执行其代码,定义条件断点等。从我与其他用户讨论的内容来看,这是从MATLAB转移到IPython时人们最想念的调试功能。

在Emacs和其他编辑器中进行IPython调试:

我不想让问题过于具体,但我主要在Emacs中工作,所以我想知道是否有任何方法将这个功能带入其中。 理想情况下,Emacs(或编辑器)应该允许程序员在代码的任何位置设置断点,并与解释器或调试器通信,以便在您选择的位置停止并将其带到完整的IPython解释器。


pdb有一个!命令,可以在断点处执行任何Python命令。 - Dmitry Galchinsky
5
我也在寻找一个类似Matlab的Python调试器!比如说,我经常在Python shell中进行原型设计。所有的变量都保存在shell中。现在我遇到了一个问题。我希望能够使用调试器来调试一小段代码,并且能够访问保存在shell中的计算变量。但是新的调试器无法访问旧的变量,这对于原型设计来说并不方便。 - user1914692
1
对于Emacs用户来说,RealGUD拥有非常好的界面。 - Clément
1
谢谢@Clément, 我已经关注这个存储库一个月了,非常激动于这个项目 :) 我还没有尝试它,但是一旦我尝试了(或者你尝试了),请随意在这里写下一个答案,可能显示如何完成所请求的内容。供其他人参考,URL为https://github.com/rocky/emacs-dbgr - Amelio Vazquez-Reina
@Clément 如果你有使用RealGUD和ipdb的经验,我尝试按照这里 https://github.com/rocky/emacs-dbgr/issues/96 的说明使用它,但没有成功。 - Amelio Vazquez-Reina
很棒,你打开了一个工单 :) 看起来你的问题也得到了解决 ^^ - Clément
16个回答

122

ipdb.set_trace()是什么?在你的代码中:

import ipdb; ipdb.set_trace()

更新:现在在Python 3.7中,我们可以写breakpoint()。它的作用相同,但它也遵守PYTHONBREAKPOINT环境变量。这个功能来自于这个PEP

这允许完全检查您的代码,并且您可以访问诸如c(继续)、n(执行下一行)、s(进入方法点)等命令。

请参见ipdb repoa list of commandsIPython现在被称为(编辑:部分)Jupyter


请注意,ipdb命令优先于Python代码。因此,为了写出list(foo),您需要print(list(foo))!list(foo)
此外,如果您喜欢ipython提示符(其emacs和vim模式、历史记录、自动完成等),则很容易为您的项目获得相同的功能,因为它基于python prompt toolkit

10
这是做法。 - j08lue
我的答案现在包括如何使用RealGUDisend-mode在Emacs中使用ipdb来完全满足OP的要求。 - Amelio Vazquez-Reina
1
Jupyter不是IPython的替代品,而是IPython Notebook的替代品。Jupyter笔记本在后台使用内核。对于Python笔记本,内核通常是IPython内核。 IPython项目继续发展。 - F-A
3
breakpoint() 很棒。在 PyCharm 中,它甚至可以直接进入 PyCharm 调试器。同时,它也是一个从控制台中粘贴的函数快速进入 PyCharm 调试器的方法。 - Richard Möhn
更新后,我可以简单地设置环境变量 PYTHONBREAKPOINT=IPython.embed,然后 breakpoint() 就能胜任了。 - Hritik

84
你可以使用IPython的%pdb魔术命令。只需在IPython中调用%pdb,当出现错误时,就会自动跳转到ipdb。虽然你不会立即执行步骤,但之后你会进入ipdb
这使得调试单个函数变得容易,因为你可以使用%load加载文件,然后运行一个函数。你可以在正确的位置使用assert强制发生错误。 %pdb是一种行魔法。调用它时,可以使用%pdb on%pdb 1%pdb off%pdb 0。如果没有参数调用,则作为切换工作。

6
这是真正的答案。 - Cesar
有类似的工具吗,使得pdb在不需要先进入IPython的情况下具有类似于IPython的功能? - Lucas
5
你也可以使用 ipython --pdb file.py -- args 启动程序,在出现异常时会进入 ipdb 调试器,这可能值得添加到答案中。 - sebastian

41

(2016年5月28日更新) 在Emacs中使用RealGUD

针对任何在Emacs中的用户,这个帖子展示了如何使用RealGUD完成原帖中提到的所有功能(甚至更多),这是一个可以与任何调试器(包括ipdb)一起使用的重要的新调试器。

  1. Emacs包isend-mode

这两个软件包的组合极其强大,可精确地重现原帖中描述的行为,并且可以做得更多。

有关RealGUD进行ipdb调试的更多信息,请参阅wiki文章


原来的回答:

尝试了许多不同的Python调试方法(包括本帖中提到的所有方法)后,我发现使用嵌入式shell的IPython调试Python是我的首选之一。

定义自定义嵌入式IPython shell:

将以下内容添加到脚本中的PYTHONPATH,以便使方法ipsh()可用。

import inspect

# First import the embed function
from IPython.terminal.embed import InteractiveShellEmbed
from IPython.config.loader import Config

# Configure the prompt so that I know I am in a nested (embedded) shell
cfg = Config()
prompt_config = cfg.PromptManager
prompt_config.in_template = 'N.In <\\#>: '
prompt_config.in2_template = '   .\\D.: '
prompt_config.out_template = 'N.Out<\\#>: '

# Messages displayed when I drop into and exit the shell.
banner_msg = ("\n**Nested Interpreter:\n"
"Hit Ctrl-D to exit interpreter and continue program.\n"
"Note that if you use %kill_embedded, you can fully deactivate\n"
"This embedded instance so it will never turn on again")   
exit_msg = '**Leaving Nested interpreter'

# Wrap it in a function that gives me more context:
def ipsh():
    ipshell = InteractiveShellEmbed(config=cfg, banner1=banner_msg, exit_msg=exit_msg)

    frame = inspect.currentframe().f_back
    msg   = 'Stopped at {0.f_code.co_filename} at line {0.f_lineno}'.format(frame)

    # Go back one level! 
    # This is needed because the call to ipshell is inside the function ipsh()
    ipshell(msg,stack_depth=2)

那么,每当我想要调试代码时,我会将 ipsh() 放置在需要进行对象检查的位置。例如,假设我想要调试下面的my_function

如何使用:

def my_function(b):
  a = b
  ipsh() # <- This will embed a full-fledged IPython interpreter
  a = 4

然后我以以下一种方式之一调用my_function(2)

  1. 通过从Unix shell运行调用此函数的Python程序
  2. 或直接从IPython中调用

无论我如何调用它,解释器都会在代码中出现ipsh()代码行处停止。完成后,您可以执行Ctrl-D,Python将恢复执行(包括您进行的任何变量更新)。请注意,如果您从常规IPython(上述情况2)运行代码,则新的IPython shell将嵌套在您调用它的shell内部,这是完全可以的,但最好要知道。无论哪种方式,一旦解释器停在ipsh()位置,我都可以检查a的值(应该是2),查看定义的函数和对象等。

问题:

上述解决方案可用于使Python在代码中的任何位置停止,并让您进入一个功能完备的IPython解释器。不幸的是,它不允许您在调用脚本后添加或删除断点,这非常令人沮丧。在我看来,这是阻止IPython成为Python的重要调试工具的唯一原因。

目前可行的最佳解决方案:

一种解决方法是预先将ipsh()放置在您希望Python解释器启动IPython shell(即断点)的不同位置。然后,您可以使用Ctrl-D在不同的预定义硬编码的“断点”之间“跳转”,这将退出当前嵌入式IPython shell,并再次在解释器遇到下一个ipsh()调用时停止。

如果您选择这条路线,一种退出“调试模式”并忽略所有后续断点的方法是使用ipshell.dummy_mode = True,这将使Python忽略我们上面创建的ipshell对象的任何后续实例化。


2
请在您找到更好的解决方案时更新此内容。但这看起来很不错,我会使用它。 - Phani
1
你在哪里/如何定义/导入cfg、banner_msg、exit_msg和inspect? - Gordon Bean
1
我需要在它之前添加 import inspect 才能使它正常工作。但是对我来说,命令行定制似乎出了点问题。 - Pascal
7
为什么不直接使用 import ipdb; ipdb.set_trace() 呢?也许我有所遗漏,但我并没有看出你更加复杂的方法的优势。 - luator
1
@BerkU。我可以使用ipdb.set_trace执行任何Python命令。至少我从未注意到任何限制,并且我经常使用它。 - luator
显示剩余9条评论

19
您可以从pudb开始IPython会话,并根据需要返回调试会话。顺便说一句,ipdb在后台使用IPython,您实际上可以使用IPython功能,例如TAB完成和魔术命令(以%开头的命令)。如果您满意ipdb,则可以使用诸如%run%debug之类的命令从IPython启动它。与纯IPython相比,ipdb会话在跟踪堆栈等方面更好。在“对象检查”中ipdb缺少什么?此外,捆绑在Emacs >= 24.3中的python.el具有出色的ipdb支持。

1
谢谢 tkf。我是你的 Emacs-Jedi 包的大粉丝。当您说 Emacs 24.3 对 ipdb 有不错的支持时,您介绍一下吗? 我通常在单独的 M-x ansi-term 缓冲区中启动 IPython,然后使用 isend-mode 将我的源缓冲区绑定到 IPython 缓冲区,以便可以使用自动发送 %paste 魔法到 IPython 缓冲区的键盘快捷键将代码发送到 IPython 解释器中进行快速测试。我总是从这个 IPython shell 中运行我的程序并使用 run,并使用 embed() 停止。 - Amelio Vazquez-Reina
3
举个例子,当你逐步执行代码时,源代码会在另一个缓冲区中打开,并用箭头指示当前执行点。你还可以像isend-mode一样使用send-region命令。 - tkf
感谢@tkf。我该如何使用python.el开始一个ipdb调试会话,并将send-region等内容发送到相应的shell?一般来说,我在哪里可以找到更多相关信息? - Amelio Vazquez-Reina
我使用%run%debug。我猜import ipdb; ipdb.set_trace()也可以。我不认为你可以将多行发送到ipdb。这是ipdb的限制。但可能%paste技巧有效。您可能需要向IPython开发人员发送功能请求。 - tkf

14

看起来@gaborous答案中的方法已经过时

新方法似乎是:

from IPython.core import debugger
debug = debugger.Pdb().set_trace

def buggy_method():
    debug()

这个功能允许你在调试时,在IPython shell中使用ipdb语句吗? - alpha_989

8
你可以从ipdb中启动IPython

引发ipdb调试器1

import idpb; ipdb.set_trace()

ipdb>控制台中以内置的方式进入IPython2

from IPython import embed; embed()

IPython内从ipdb>控制台返回:

exit

如果你运气好,正在使用Emacs,事情可以变得更加方便。

这需要使用M-x shell。使用yasnippetbm,定义以下片段。这将用set-trace行替换编辑器中的ipdb文本。插入片段后,该行将被突出显示,以便轻松地进行注意和导航。使用M-x bm-next进行导航。

# -*- mode: snippet -*-
# name: ipdb
# key: ipdb
# expand-env: ((yas-after-exit-snippet-hook #'bm-toggle))
# --
import ipdb; ipdb.set_trace()

1 所有内容都放在一行中,以便轻松删除。 由于imports仅发生一次,这种形式确保只需要当您需要ipdb时才导入它,没有额外的开销。

2 您可以通过在.pdbrc文件中导入IPython参见此处)来节省输入时间:

try:
    from IPython import embed
except:
    pass

这样你就可以从ipdb中简单地调用embed()(当然,前提是安装了IPython)。

7
在pdb中,将"!"符号添加到您键入的命令前似乎具有与在IPython shell中执行某些操作相同的效果。这适用于访问特定函数或变量名称的帮助信息。也许这可以在一定程度上帮助您。例如:
ipdb> help(numpy.transpose)
*** No help on (numpy.transpose)

但是!使用 !help(numpy.transpose) 命令可以获得 numpy.transpose 的帮助页面。同样,对于变量名,例如您有一个变量 l,在 pdb 中输入 "l" 会列出代码,但是使用 !l 命令则会打印出 l 的值。


3
这是一个令人头疼的pdb陷阱,值得注意。 - Wilfred Hughes

5

这个问题的正确,简单,酷炫,准确答案是使用带有-d标志的%run宏。

In [4]: run -d myscript.py
NOTE: Enter 'c' at the ipdb>  prompt to continue execution.        
> /cygdrive/c/Users/mycodefolder/myscript.py(4)<module>()
      2                                                            
      3                        
----> 4 a=1                                            
      5 b=2

1
这应该放得更高些。如果我需要在代码中(模块内)插入一行,我需要重新启动IPython以重新加载模块。有了这个,我只需%save /tmp/foo.py x-y所需运行和调试的代码,然后%run -d /tmp/foo.py将断点设置在我喜欢的任何位置。非常好的答案! - Romeo Valentin

4
你试过这个提示了吗?

Or better still, use ipython, and call:

from IPython.Debugger import Tracer; debug_here = Tracer()

then you can just use

debug_here()

whenever you want to set a breakpoint


ModuleNotFoundError: No module named 'IPython.Debugger' - John Greene
@JohnGreene 这是一个非常老的答案,现在IPython已经更名为Jupyter并且API发生了很大变化。 - gaborous

3

一种选择是使用像Spyder这样的集成开发环境,它应该允许您在调试时与您的代码交互(实际上是使用IPython控制台)。事实上,Spyder非常类似于MATLAB,我想这是有意为之的。它包括变量检查器、变量编辑、内置文档访问等功能。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接