tl;dr:即使您已经进入外部异常的事后调试,您也可以调试内部异常。操作方法如下:
- 进入交互模式从
pdb
(type interact
进入pdb
迅速的)。
- Run:
import pdb, sys; pdb.post_mortem(sys.last_value.__context__.__traceback__)
Note:
- Replace
__context__
with __cause__
如果您的异常是显式链接的;还追加更多__context__
s or __cause__
s 如果嵌套得更深。
- 如果您正在检查已处理的异常(在 try-catch 中捕获的异常),请替换
sys.last_value
with sys.exc_info()[1]
。如果您不确定,请在继续之前检查异常值。 (谢谢@医生在评论中指出这一点)
- 这开始了一个新的
pdb
允许您调试内部异常的会话。
以下是对这项工作为何有效的详细解释。在深入讨论解决方案之前,我首先解释一些相关概念:
连锁异常
这里的“例外中的例外”被称为连锁异常。异常可以显式或隐式链接:
>>>: try:
...: raise ZeroDivisionError
...: except Exception as inner_exc:
...: raise ValueError # implicit chaining
...:
---------------------------------------------------------------------------
ZeroDivisionError Traceback (most recent call last)
<ipython-input-6-1ae22e81c853> in <module>
1 try:
----> 2 raise ZeroDivisionError
3 except Exception as inner_exc:
ZeroDivisionError:
During handling of the above exception, another exception occurred:
ValueError Traceback (most recent call last)
<ipython-input-6-1ae22e81c853> in <module>
2 raise ZeroDivisionError
3 except Exception as inner_exc:
----> 4 raise ValueError # implicit chaining
ValueError:
>>>: try:
...: raise ZeroDivisionError
...: except Exception as inner_exc:
...: raise ValueError from inner_exc # explicit chaining
...:
---------------------------------------------------------------------------
ZeroDivisionError Traceback (most recent call last)
<ipython-input-5-63c49fcb10a2> in <module>
1 try:
----> 2 raise ZeroDivisionError
3 except Exception as inner_exc:
ZeroDivisionError:
The above exception was the direct cause of the following exception:
ValueError Traceback (most recent call last)
<ipython-input-5-63c49fcb10a2> in <module>
2 raise ZeroDivisionError
3 except Exception as inner_exc:
----> 4 raise ValueError from inner_exc # explicit chaining
ValueError:
如果我们将外部异常捕获为outer_exc
,然后我们可以通过检查内部异常outer_exc.__cause__
(如果显式链接)或outer_exc.__context__
(如果隐式链接)。
事后调试
运行脚本python -m pdb
允许Python调试器进入事后调试异常模式。这里的“事后”是指“异常发生后”。您可以通过运行以下命令从 IPython 控制台或 Jupyter 笔记本中执行相同的操作%debug
magic.
如果您有权访问回溯对象,您还可以手动进入事后调试模式。幸运的是,回溯对象存储在异常对象本身上__traceback__
属性:
>>> try:
... raise ZeroDivisionError:
... except Exception as e:
... # Variable `e` is local to this block, so we store it in another variable
... # to extend its lifetime.
... exc = e
>>> import pdb
>>> pdb.post_mortem(exc.__traceback__)
> <ipython-input-8-e5b5ed89e466>(2)<module>()
-> raise ZeroDivisionError
(Pdb)
调试链式异常
现在我们可以尝试调试链式异常!假设我们已经处于外部异常的事后调试模式。我们需要做的是:
- 获取外部异常对象;
- 访问内部异常对象,并获取其回溯;
- Call
pdb.post_mortem()
在该回溯对象上。
这就是我们所做的:
# First, enter interactive mode to execute commands.
(Pdb) interact
*interactive*
# The current exception is stored in `sys.exc_info()`. This gives back a tuple
# of (exception type, exception value, traceback).
>>> import sys
>>> sys.exc_info()
(<class 'AssertionError'>, AssertionError(), <traceback object at 0x10c683e00>)
>>> sys.exc_info()[1]
AssertionError()
# In our case, the inner exception is implicitly chained. Access it through
# the `__context__` attribute.
>>> sys.exc_info()[1].__context__
ZeroDivisionError('division by zero')
# Get its traceback, and enter post-mortem debugging.
>>> sys.exc_info()[1].__context__.__traceback__
<traceback object at 0x10c683c80>
>>> import pdb
>>> pdb.post_mortem(sys.exc_info()[1].__context__.__traceback__)
> test.py(2)f()
-> x / x
(Pdb)
你有它!您现在可以使用正常调试内部异常pdb
命令,例如遍历堆栈或检查局部变量。