【问题标题】:How can I "jump" into stackframe from exception?如何从异常“跳转”到堆栈帧?
【发布时间】:2017-01-31 17:24:12
【问题描述】:

有一个提出的exception 我想跳进那个框架。为了更好地解释我的意思,我写了这个 mwe:

假设我有以下代码:

from multiprocessing import Pool
import sys

# Setup debugger
def raiseDebugger(*args):
    """ http://code.activestate.com/recipes/65287-automatically-start-the-
    debugger-on-an-exception/ """

    import traceback, pdb
    traceback.print_exception(*args)
    pdb.pm()

sys.excepthook = raiseDebugger


# Now start with the question

def faulty(i):
    return 1 / i


with Pool() as pool:
    pool.map(faulty, range(6))

不出所料地导致:

multiprocessing.pool.RemoteTraceback:
"""
Traceback (most recent call last):
  File "/home/bin/conda/lib/python3.5/multiprocessing/pool.py", line 119, in worker
    result = (True, func(*args, **kwds))
  File "/home/bin/conda/lib/python3.5/multiprocessing/pool.py", line 44, in mapstar
    return list(map(*args))
  File "test2.py", line 19, in faulty
    return 1 / i
ZeroDivisionError: division by zero
"""

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "test2.py", line 23, in <module>
    pool.map(faulty, range(6))
  File "/home/bin/conda/lib/python3.5/multiprocessing/pool.py", line 260, in map
    return self._map_async(func, iterable, mapstar, chunksize).get()
  File "/home/bin/conda/lib/python3.5/multiprocessing/pool.py", line 608, in get
    raise self._value
ZeroDivisionError: division by zero
> /home/bin/conda/lib/python3.5/multiprocessing/pool.py(608)get()
-> raise self._value
(Pdb)

现在要调试问题,我想“跳转”到最初引发 exception (ZeroDivisionError) 的框架。

原来的例外在self._value 下仍然可用,加上self._value.__traceback__

【问题讨论】:

    标签: python python-3.x debugging exception


    【解决方案1】:

    pm(或post_mortem)调用来自sys.exc_info的值字段,post_mortem的默认调用是在该值的__traceback__上完成的。但是,如果您想访问底层对象,您需要访问它的__context__。鉴于此代码示例:

    import pdb
    import sys
    import traceback
    
    def top():
        value = 1
        raise Exception('this always fails')
    
    def bottom():
        try:
            top()
        except Exception as bot_ex:
            x = {}
            return x['nothing']
    
    try:
        bottom()
    except Exception as main_ex:
        pdb.post_mortem()
    

    运行代码。 main_ex 类似于您的 self._value

    > /tmp/foo.py(14)bottom()
    -> return x['nothing']
    (Pdb) main_ex
    KeyError('nothing',)
    (Pdb) pdb.post_mortem(main_ex.__traceback__)
    > /tmp/foo.py(14)bottom()
    -> return x['nothing']
    

    请注意,我们在同一位置有一个新的 pdb 提示符,这是最初引发异常的地方。如果我们需要更进一步,让我们用__context__ 试试吧:

    (Pdb) c
    (Pdb) pdb.post_mortem(main_ex.__context__.__traceback__)
    > /tmp/foo.py(7)top()
    -> raise Exception('this always fails')
    

    如果需要,请不断重复,直到达到所需的目标上下文/回溯。


    现在对于多处理案例,我不知道会有这么大的不同,因为这个问题暗示了一些一般性的东西(我怎样才能从异常“跳转”到堆栈帧?),但事实证明@中的细节987654335@ 与众不同。

    在 Python 3.4 中,一个解决方法只是将回溯显示为字符串;由于回溯实际上有多少东西,如 Python 跟踪器上的 issue 13831 中所讨论的那样,传达所有被证明是困难的东西,因此做了一个 hack 以将 __cause__ 属性带入当前异常,但它是没有完整的__traceback__,因为它只有字符串表示,正如我所怀疑的那样。

    无论如何,这就是会发生的事情:

    (Pdb) !import pdb
    (Pdb) !self._value.__cause__
    RemoteTraceback('\n"""\nTraceback (most recent call last):...',)
    (Pdb) !type(self._value.__cause__)
    <class 'multiprocessing.pool.RemoteTraceback'>
    (Pdb) !self._value.__cause__.__traceback__
    (Pdb) !self._value.__cause__.__context__
    

    因此,在他们弄清楚如何使所有这些状态跨越进程边界之前,这实际上是不可能的。

    【讨论】:

    • 感谢您的回答。我确实用我的例子尝试过,但是self._value.__context__None。这是由于引发了异常而不是重新引发(无参数引发)吗?
    • 哦,嗯,虽然远程回溯看起来像一个字符串,但考虑到你得到的(self._value 除外)你可能只想用self._value.__traceback__ 来做。很抱歉没有让我的回答更清楚。
    • 我试过了。但是,它在主进程中使用raise self._value 加载框架。我想这是由于多处理造成的限制。我肯定从您的回答中学到了一些东西,并且会接受它,除非有人在第二天找到解决我问题的方法。
    猜你喜欢
    • 2010-11-28
    • 2013-10-25
    • 2016-01-12
    • 2010-10-19
    • 1970-01-01
    • 2013-10-07
    • 2015-01-03
    • 2012-10-15
    • 2021-06-19
    相关资源
    最近更新 更多