>>> 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
需要一个参数,就是输出错误的模式,有三种选择:Plain
,Context
和Verbose
。默认是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命令。
让我们查看最近发生的那个异常,然后执行一些基础的指令来打印变量a
和b
的值,最后使用quit
退出调试模式:
>>> %debug
> [0;32m/tmp/ipykernel_46120/4021589855.py[0m(2)[0;36mfunc1[0;34m()[0m
[0;32m 1 [0;31m[0;32mdef[0m [0mfunc1[0m[0;34m([0m[0ma[0m[0;34m,[0m [0mb[0m[0;34m)[0m[0;34m:[0m[0;34m[0m[0;34m[0m[0m
[0m[0;32m----> 2 [0;31m [0;32mreturn[0m [0ma[0m [0;34m/[0m [0mb[0m[0;34m[0m[0;34m[0m[0m
[0m[0;32m 3 [0;31m[0;34m[0m[0m
[0m[0;32m 4 [0;31m[0;32mdef[0m [0mfunc2[0m[0;34m([0m[0mx[0m[0;34m)[0m[0;34m:[0m[0;34m[0m[0;34m[0m[0m
[0m[0;32m 5 [0;31m [0ma[0m [0;34m=[0m [0mx[0m[0;34m[0m[0;34m[0m[0m
[0m
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
> [0;32m/tmp/ipykernel_46120/4021589855.py[0m(2)[0;36mfunc1[0;34m()[0m
[0;32m 1 [0;31m[0;32mdef[0m [0mfunc1[0m[0;34m([0m[0ma[0m[0;34m,[0m [0mb[0m[0;34m)[0m[0;34m:[0m[0;34m[0m[0;34m[0m[0m
[0m[0;32m----> 2 [0;31m [0;32mreturn[0m [0ma[0m [0;34m/[0m [0mb[0m[0;34m[0m[0;34m[0m[0m
[0m[0;32m 3 [0;31m[0;34m[0m[0m
[0m[0;32m 4 [0;31m[0;32mdef[0m [0mfunc2[0m[0;34m([0m[0mx[0m[0;34m)[0m[0;34m:[0m[0;34m[0m[0;34m[0m[0m
[0m[0;32m 5 [0;31m [0ma[0m [0;34m=[0m [0mx[0m[0;34m[0m[0;34m[0m[0m
[0m
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
> [0;32m<ipython-input-1-586ccabd0db3>[0m(2)[0;36mfunc1[0;34m()[0m
[0;32m 1 [0;31m[0;32mdef[0m [0mfunc1[0m[0;34m([0m[0ma[0m[0;34m,[0m [0mb[0m[0;34m)[0m[0;34m:[0m[0;34m[0m[0;34m[0m[0m
[0m[0;32m----> 2 [0;31m [0;32mreturn[0m [0ma[0m [0;34m/[0m [0mb[0m[0;34m[0m[0;34m[0m[0m
[0m[0;32m 3 [0;31m[0;34m[0m[0m
[0m[0;32m 4 [0;31m[0;32mdef[0m [0mfunc2[0m[0;34m([0m[0mx[0m[0;34m)[0m[0;34m:[0m[0;34m[0m[0;34m[0m[0m
[0m[0;32m 5 [0;31m [0ma[0m [0;34m=[0m [0mx[0m[0;34m[0m[0;34m[0m[0m
[0m
ipdb> print(b)
0
ipdb> quit
最后,如果你有一个Python脚本文件,然后希望在IPython中交互式运行,并且打开调试器的话,你可以使用%run -d
魔术指令来执行这个脚本,然后你还能在调试模式提示符下使用next
命令来单步执行脚本中的代码。
调试命令部分列表¶
除了下面列出来的最常用的命令和简单解释之外,还有很多由于篇幅原因未列出说明的调试命令。
调试命令 |
描述 |
---|---|
|
显示当前在文件中的位置信息 |
|
查看 帮助文档,可以显示列表,或查看某个命令的具体帮助信息 |
|
退出调试模式提示符 |
`` c(ontinue)`` |
退出调试模式,继续执行代码 |
|
执行下一行代码,单步调试 |
|
直接重复执行上一条命令 |
|
打印变量内容 |
|
跟踪进入子函数内部进行调试 |
|
直接执行到函数返回 |
需要了解更多信息,可以在调试器模式下使用help
命令,或者参见ipdb
的在线文档。