Pdb 在异常中转到异常中的帧
Pdb go to a frame in exception within exception
我正在使用 pdb
调试名为 a.py
的程序
def f(x) :
x / x
def g(x) :
try :
f(x)
except Exception as e :
assert 0
g(0)
当我运行程序使用python3 -m pdb a.py
时,程序停在assert 0
行,我得到以下错误信息:
Traceback (most recent call last):
File "/tmp/a.py", line 6, in g
f(x)
File "/tmp/a.py", line 2, in f
x / x
ZeroDivisionError: division by zero
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/usr/lib64/python3.6/pdb.py", line 1667, in main
pdb._runscript(mainpyfile)
File "/usr/lib64/python3.6/pdb.py", line 1548, in _runscript
self.run(statement)
File "/usr/lib64/python3.6/bdb.py", line 434, in run
exec(cmd, globals, locals)
File "<string>", line 1, in <module>
File "/tmp/a.py", line 11, in <module>
g(0)
File "/tmp/a.py", line 9, in g
assert 0
AssertionError
堆栈是(使用 bt
命令显示):
(Pdb) bt
/usr/lib64/python3.6/pdb.py(1667)main()
-> pdb._runscript(mainpyfile)
/usr/lib64/python3.6/pdb.py(1548)_runscript()
-> self.run(statement)
/usr/lib64/python3.6/bdb.py(434)run()
-> exec(cmd, globals, locals)
<string>(1)<module>()->None
/tmp/a.py(11)<module>()->None
-> g(0)
> /tmp/a.py(9)g()
-> assert 0
(Pdb)
问题是,我无法仅使用 up
和 down
去函数 f 调试 x / x
,因为我的堆栈在 g 函数处结束。
我应该如何在异常中调试这样的异常?例外中的例外中的例外......?
实际重新引发异常( 中的选项 3
wholevinski's answer) 解决了我的问题,因为它不需要我修改函数 f。
这是代码:
'''Option 3'''
def f(x) :
x -= 1
x / x
def g(x) :
try :
for i in range(x, 0, -1) :
print(f(i))
except Exception as e :
raise e
g(10)
栈的pdb输出:
(Pdb) bt
/usr/lib64/python3.6/pdb.py(1667)main()
-> pdb._runscript(mainpyfile)
/usr/lib64/python3.6/pdb.py(1548)_runscript()
-> self.run(statement)
/usr/lib64/python3.6/bdb.py(434)run()
-> exec(cmd, globals, locals)
<string>(1)<module>()->None
/tmp/a.py(13)<module>()->None
-> g(10)
/tmp/a.py(11)g()
-> raise e
/tmp/a.py(9)g()
-> print(f(i))
> /tmp/a.py(4)f()
-> x / x
(Pdb)
tl;dr: 即使您已经为外部异常输入了 post-mortem 调试,您仍然可以调试内部异常。方法如下:
- 从
pdb
进入交互模式(在 pdb
提示符中输入 interact
)。
- 运行:
import pdb, sys; pdb.post_mortem(sys.last_value.__context__.__traceback__)
注:
- 如果您的异常被显式链接,请将
__context__
替换为 __cause__
;如果嵌套更深,还可以附加更多 __context__
s 或 __cause__
s。
- 如果您正在检查已处理的异常(在 try-catch 中捕获的异常),请将
sys.last_value
替换为 sys.exc_info()[1]
。如果您不确定,请在继续之前检查异常值。 (感谢 @The Doctor 在评论中指出这一点)
- 这将启动一个新的
pdb
会话,允许您调试内部异常。
以下是对这项工作的原因的详细解释。在深入解决方案之前,我将首先解释一些相关概念:
链式异常
这里的“例外中的例外”被称为chained exceptions。异常可以显式或隐式链接:
>>>: 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__
(如果隐式链接)检查内部异常。
Post-死机调试
运行使用 python -m pdb
设置脚本允许 Python 调试器在出现异常时进入 post-mortem debugging 模式。 “Post-mortem”这里的意思是“发生异常之后”。您可以通过 IPython 控制台或在 Jupyter notebooks 中通过 运行 %debug
魔法执行相同的操作。
如果您有权访问回溯对象,您也可以手动进入 post-mortem 调试模式。幸运的是,回溯对象作为 __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)
调试链式异常
现在我们可以尝试调试链式异常了!假设我们已经处于外部异常的 post-mortem 调试模式。我们需要做的是:
- 获取外部异常对象;
- 访问内部异常对象,并获取其回溯;
- 对该回溯对象调用
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__)
> /Volumes/hrt/trunk-3/a.py(2)f()
-> x / x
(Pdb)
给你!您现在可以使用正常的 pdb
命令调试内部异常,例如遍历堆栈或检查局部变量。
我正在使用 pdb
调试名为a.py
的程序
def f(x) :
x / x
def g(x) :
try :
f(x)
except Exception as e :
assert 0
g(0)
当我运行程序使用python3 -m pdb a.py
时,程序停在assert 0
行,我得到以下错误信息:
Traceback (most recent call last):
File "/tmp/a.py", line 6, in g
f(x)
File "/tmp/a.py", line 2, in f
x / x
ZeroDivisionError: division by zero
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/usr/lib64/python3.6/pdb.py", line 1667, in main
pdb._runscript(mainpyfile)
File "/usr/lib64/python3.6/pdb.py", line 1548, in _runscript
self.run(statement)
File "/usr/lib64/python3.6/bdb.py", line 434, in run
exec(cmd, globals, locals)
File "<string>", line 1, in <module>
File "/tmp/a.py", line 11, in <module>
g(0)
File "/tmp/a.py", line 9, in g
assert 0
AssertionError
堆栈是(使用 bt
命令显示):
(Pdb) bt
/usr/lib64/python3.6/pdb.py(1667)main()
-> pdb._runscript(mainpyfile)
/usr/lib64/python3.6/pdb.py(1548)_runscript()
-> self.run(statement)
/usr/lib64/python3.6/bdb.py(434)run()
-> exec(cmd, globals, locals)
<string>(1)<module>()->None
/tmp/a.py(11)<module>()->None
-> g(0)
> /tmp/a.py(9)g()
-> assert 0
(Pdb)
问题是,我无法仅使用 up
和 down
去函数 f 调试 x / x
,因为我的堆栈在 g 函数处结束。
我应该如何在异常中调试这样的异常?例外中的例外中的例外......?
实际重新引发异常( 中的选项 3 wholevinski's answer) 解决了我的问题,因为它不需要我修改函数 f。 这是代码:
'''Option 3'''
def f(x) :
x -= 1
x / x
def g(x) :
try :
for i in range(x, 0, -1) :
print(f(i))
except Exception as e :
raise e
g(10)
栈的pdb输出:
(Pdb) bt
/usr/lib64/python3.6/pdb.py(1667)main()
-> pdb._runscript(mainpyfile)
/usr/lib64/python3.6/pdb.py(1548)_runscript()
-> self.run(statement)
/usr/lib64/python3.6/bdb.py(434)run()
-> exec(cmd, globals, locals)
<string>(1)<module>()->None
/tmp/a.py(13)<module>()->None
-> g(10)
/tmp/a.py(11)g()
-> raise e
/tmp/a.py(9)g()
-> print(f(i))
> /tmp/a.py(4)f()
-> x / x
(Pdb)
tl;dr: 即使您已经为外部异常输入了 post-mortem 调试,您仍然可以调试内部异常。方法如下:
- 从
pdb
进入交互模式(在pdb
提示符中输入interact
)。 - 运行:
注:import pdb, sys; pdb.post_mortem(sys.last_value.__context__.__traceback__)
- 如果您的异常被显式链接,请将
__context__
替换为__cause__
;如果嵌套更深,还可以附加更多__context__
s 或__cause__
s。 - 如果您正在检查已处理的异常(在 try-catch 中捕获的异常),请将
sys.last_value
替换为sys.exc_info()[1]
。如果您不确定,请在继续之前检查异常值。 (感谢 @The Doctor 在评论中指出这一点)
- 如果您的异常被显式链接,请将
- 这将启动一个新的
pdb
会话,允许您调试内部异常。
以下是对这项工作的原因的详细解释。在深入解决方案之前,我将首先解释一些相关概念:
链式异常
这里的“例外中的例外”被称为chained exceptions。异常可以显式或隐式链接:
>>>: 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__
(如果隐式链接)检查内部异常。
Post-死机调试
运行使用 python -m pdb
设置脚本允许 Python 调试器在出现异常时进入 post-mortem debugging 模式。 “Post-mortem”这里的意思是“发生异常之后”。您可以通过 IPython 控制台或在 Jupyter notebooks 中通过 运行 %debug
魔法执行相同的操作。
如果您有权访问回溯对象,您也可以手动进入 post-mortem 调试模式。幸运的是,回溯对象作为 __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)
调试链式异常
现在我们可以尝试调试链式异常了!假设我们已经处于外部异常的 post-mortem 调试模式。我们需要做的是:
- 获取外部异常对象;
- 访问内部异常对象,并获取其回溯;
- 对该回溯对象调用
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__)
> /Volumes/hrt/trunk-3/a.py(2)f()
-> x / x
(Pdb)
给你!您现在可以使用正常的 pdb
命令调试内部异常,例如遍历堆栈或检查局部变量。