PDB在异常内转到异常帧

时间:2018-08-29 16:11:49

标签: python exception-handling 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) 

问题是,我不能仅使用x / xup到函数f来调试down,因为我的堆栈在g函数处结束。

如何在异常中调试此类异常?异常内的异常又如何呢?

3 个答案:

答案 0 :(得分:3)

tl;dr: 即使您已经进入外部异常的事后调试,您也可以调试内部异常。操作方法如下:

  1. pdb 进入交互模式(在 interact 提示中输入 pdb)。
  2. 运行:
    import pdb, sys; pdb.post_mortem(sys.last_value.__context__.__traceback__)
    
    注意:
    • 如果您的异常被显式链接,请将 __context__ 替换为 __cause__;如果嵌套更深,还可以附加更多 __context____cause__
    • 如果您正在检查已处理的异常(在 try-catch 中捕获的异常),请将 sys.last_value 替换为 sys.exc_info()[1]。如果您不确定,请在继续之前检查异常值。 (感谢 @The Doctor 在评论中指出这一点)
  3. 这将启动一个新的 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__(如果隐式链接)检查内部异常。


事后调试

使用 python -m pdb 运行脚本允许 Python 调试器在出现异常时进入 post-mortem debugging 模式。 “验尸”在这里的意思是“在异常发生之后”。您可以通过运行 %debug 魔法从 IPython 控制台或在 Jupyter 笔记本中执行相同的操作。

如果您有权访问回溯对象,也可以手动进入事后调试模式。幸运的是,回溯对象作为 __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)

调试链式异常

现在我们可以尝试调试链式异常了!假设我们已经处于外部异常的事后调试模式。我们需要做的是:

  1. 获取外部异常对象;
  2. 访问内部异常对象,并获取其回溯;
  3. 对该回溯对象调用 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 命令调试内部异常,例如遍历堆栈或检查局部变量。

答案 1 :(得分:1)

如我所见,那么您有几个选择。您可以将try-except移至f(x),在g() try-except中打印一个描述性错误并放入pdb,或者在记录友好错误消息后重新引发异常。我不喜欢依靠未捕获的异常进行调试(选项3),因此选项1和2如下所示:

'''Option 1'''
def f(x) :
    try:
        x / x
    except Exception as e:
        print('An error occurred, entering pdb shell')
        import pdb; pdb.set_trace()

def g(x) :
    f(x)

g(0)

输出:

$ python throw.py 
An error occurred, entering pdb shell
--Return--
> /home/wholevinski/so_test/throw.py(6)f()->None
-> import pdb; pdb.set_trace()
(Pdb) x
0

或:

'''Option 2'''
def f(x):
    x / x

def g(x):
    try:
        f(x)
    except Exception as e:
        print('f({}) raised with error "{}". Entering pdb shell.'.format(x, str(e)))
        import pdb; pdb.set_trace()

g(0)

输出:

$ python throw.py 
f(0) raised with error division by zero. Entering pdb shell.
--Return--
> /home/wholevinski/so_test/throw.py(9)g()->None
-> import pdb; pdb.set_trace()
(Pdb) x

答案 2 :(得分:1)

实际上重新引发异常( wholevinskianswer中的选项3)解决了我的问题,因为它不需要我修改函数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)