>>> from env_helper import info; info()
页面更新时间: 2024-04-08 07:54:49
运行环境:
    Linux发行版本: Debian GNU/Linux 12 (bookworm)
    操作系统内核: Linux-6.1.0-18-amd64-x86_64-with-glibc2.36
    Python版本: 3.11.2

14.8. 错误和调试

开发和数据分析通常都需要很多的试验,伴随着很多的错误,IPython包含着能够将这个过程串联起来的工具。这一章节会简要介绍Python的异常控制,然后介绍在代码中调试的工具。

14.8.1. 异常控制:%xmode

大部分情况下如果Python脚本执行失败了,都是由于抛出了异常导致的。当解释器碰到了这些异常的时候,会将错误产生的原因压到当前程序执行的堆栈当中,你可以通过Python的traceback访问到这些信息。使用%xmode魔术指令,IPython允许你控制异常发生时错误信息的数量。看例子:

>>> def func1(a, b):
>>>     return a / b
>>>
>>> def func2(x):
>>>     a = x
>>>     b = x - 1
>>>     return func1(a, b)
>>> func2(1)
---------------------------------------------------------------------------

ZeroDivisionError                         Traceback (most recent call last)

Cell In [2], line 1
----> 1 func2(1)


Cell In [1], line 7, in func2(x)
      5 a = x
      6 b = x - 1
----> 7 return func1(a, b)


Cell In [1], line 2, in func1(a, b)
      1 def func1(a, b):
----> 2     return a / b


ZeroDivisionError: division by zero

调用func2会发生错误,Python解析器会使用默认方式打印出堆栈信息,通过查看这些信息,你可以检查程序发生了什么问题。默认情况下,打印出来的信息会包括很多行,每行会输出函数调用的情况。使用%xmode魔术指令(名称是Exception mode的缩写),我们可以修改打印的信息内容。

%xmode需要一个参数,就是输出错误的模式,有三种选择:PlainContextVerbose。默认是Context,该模式下的输出就如上面所见。Plain会更简短,提供更少的内容:

>>> %xmode Plain
Exception reporting mode: Plain
>>> func2(1)
Traceback (most recent call last):


  Cell In [4], line 1
    func2(1)


  Cell In [1], line 7 in func2
    return func1(a, b)


  Cell In [1], line 2 in func1
    return a / b


ZeroDivisionError: division by zero

Verbose模式会增加一些额外的信息,包括每个函数调用时候的参数值:

>>> %xmode Verbose
Exception reporting mode: Verbose
>>> func2(1)
---------------------------------------------------------------------------

ZeroDivisionError                         Traceback (most recent call last)

Cell In [6], line 1
----> 1 func2(1)


Cell In [1], line 7, in func2(x=1)
      5 a = x
      6 b = x - 1
----> 7 return func1(a, b)
        a = 1
        b = 0


Cell In [1], line 2, in func1(a=1, b=0)
      1 def func1(a, b):
----> 2     return a / b
        a = 1
        b = 0


ZeroDivisionError: division by zero

这些额外的信息能帮助你迅速定位到异常发生的原因。那么为什么我们不一直使用Verbose模式呢?如果你的代码变得复杂了之后,这种堆栈的输出会变得十分冗长。根据实际情况,有时候简短的默认模式可能更加适合查错。

14.8.2. 调试:当分析堆栈已经不足够了

标准Python解析器有一个交互式的调试工具叫做pdb。这个调试工具能让用户一行一行的执行代码,然后定位到更困难的错误原因。IPython增强版的调试器叫做ipdb

实际上存在着很多种方法来启动和使用这两个调试器;我们在这里不会完整的介绍它们。你可以参考这两个工具的在线文档来学习更多的内容。

在IPython中,也许最简单的调试方式就是使用%debug魔术指令了。如果当你遇到一个异常之后调用它,IPython会自动打开一个交互式的调试提示符,并定位在异常发生的地方。ipdb提示符允许你查看当前的堆栈信息,显示变量和它们的值,甚至执行Python命令。

让我们查看最近发生的那个异常,然后执行一些基础的指令来打印变量ab的值,最后使用quit退出调试模式:

>>> %debug
> /tmp/ipykernel_46120/4021589855.py(2)func1()
      1 def func1(a, b):
----> 2     return a / b
      3 
      4 def func2(x):
      5     a = x

ipdb>  b
ipdb>  a
a = 1
b = 0
ipdb>  b
ipdb>  a
a = 1
b = 0
ipdb>
a = 1
b = 0
ipdb>
a = 1
b = 0
ipdb>
a = 1
b = 0
ipdb>  quit

这个交互式的调试器允许我们做更多的操作,我们可以向上或向下浏览不同级别的堆栈,然后再查看那个层级的变量内容:

>>> %debug
> /tmp/ipykernel_46120/4021589855.py(2)func1()
      1 def func1(a, b):
----> 2     return a / b
      3 
      4 def func2(x):
      5     a = x

ipdb>  a
a = 1
b = 0
ipdb>  b
ipdb>
ipdb>  quit

这不仅仅能够让你迅速定位问题的原因,还能让你一直回溯到错误最上层的函数调用。

如果你希望调试器保持打开状态,每当发生异常时就自动启动,你可以使用%pdb魔术指令,使用on/off参数就能打开或关闭调试器的自动启动模式。

>>> %xmode Plain
>>> %pdb on
>>> func2(1)
Exception reporting mode: Plain
Automatic pdb calling has been turned ON
Traceback (most recent call last):


  File "<ipython-input-9-f80f6b5cecf3>", line 3, in <module>
    func2(1)


  File "<ipython-input-1-586ccabd0db3>", line 7, in func2
    return func1(a, b)


  File "<ipython-input-1-586ccabd0db3>", line 2, in func1
    return a / b


ZeroDivisionError: division by zero
> <ipython-input-1-586ccabd0db3>(2)func1()
      1 def func1(a, b):
----> 2     return a / b
      3 
      4 def func2(x):
      5     a = x

ipdb> print(b)
0
ipdb> quit

最后,如果你有一个Python脚本文件,然后希望在IPython中交互式运行,并且打开调试器的话,你可以使用%run -d魔术指令来执行这个脚本,然后你还能在调试模式提示符下使用next命令来单步执行脚本中的代码。

调试命令部分列表

除了下面列出来的最常用的命令和简单解释之外,还有很多由于篇幅原因未列出说明的调试命令。

调试命令

描述

list

显示当前在文件中的位置信息

h(elp)

查看 帮助文档,可以显示列表,或查看某个命令的具体帮助信息

q(uit)

退出调试模式提示符

`` c(ontinue)``

退出调试模式,继续执行代码

n(ext)

执行下一行代码,单步调试

<enter>

直接重复执行上一条命令

p(rint)

打印变量内容

s(tep)

跟踪进入子函数内部进行调试

r(eturn)

直接执行到函数返回

需要了解更多信息,可以在调试器模式下使用help命令,或者参见ipdb在线文档