25

使用 concurrent.futures 的示例(2.7 的反向移植):

import concurrent.futures  # line 01
def f(x):  # line 02
    return x * x  # line 03
data = [1, 2, 3, None, 5]  # line 04
with concurrent.futures.ThreadPoolExecutor(len(data)) as executor:  # line 05
    futures = [executor.submit(f, n) for n in data]  # line 06
    for future in futures:  # line 07
        print(future.result())  # line 08

输出:

1
4
9
Traceback (most recent call last):
  File "C:\test.py", line 8, in <module>
    print future.result()  # line 08
  File "C:\dev\Python27\lib\site-packages\futures-2.1.4-py2.7.egg\concurrent\futures\_base.py", line 397, in result
    return self.__get_result()
  File "C:\dev\Python27\lib\site-packages\futures-2.1.4-py2.7.egg\concurrent\futures\_base.py", line 356, in __get_result
    raise self._exception
TypeError: unsupported operand type(s) for *: 'NoneType' and 'NoneType'

字符串"...\_base.py", line 356, in __get_result"不是我希望看到的端点。是否可以获得引发异常的真实线路?就像是:

  File "C:\test.py", line 3, in f
    return x * x  # line 03

在这种情况下,Python3 似乎显示了正确的行号。为什么python2.7不能?有什么解决方法吗?

4

3 回答 3

38

我遇到了同样的情况,我真的需要追溯引发的异常。我能够开发出这种解决方法,其中包括使用 ThreadPoolExecutor.

import sys
import traceback
from concurrent.futures import ThreadPoolExecutor

class ThreadPoolExecutorStackTraced(ThreadPoolExecutor):

    def submit(self, fn, *args, **kwargs):
        """Submits the wrapped function instead of `fn`"""

        return super(ThreadPoolExecutorStackTraced, self).submit(
            self._function_wrapper, fn, *args, **kwargs)

    def _function_wrapper(self, fn, *args, **kwargs):
        """Wraps `fn` in order to preserve the traceback of any kind of
        raised exception

        """
        try:
            return fn(*args, **kwargs)
        except Exception:
            raise sys.exc_info()[0](traceback.format_exc())  # Creates an
                                                             # exception of the
                                                             # same type with the
                                                             # traceback as
                                                             # message

如果您使用此子类并运行以下代码段:

def f(x):
    return x * x

data = [1, 2, 3, None, 5]
with ThreadPoolExecutorStackTraced(max_workers=len(data)) as executor:
    futures = [executor.submit(f, n) for n in data]
    for future in futures:
        try:
            print future.result()
        except TypeError as e:
            print e

输出将类似于:

1
4
9
Traceback (most recent call last):
  File "future_traceback.py", line 17, in _function_wrapper
    return fn(*args, **kwargs)
  File "future_traceback.py", line 24, in f
    return x * x
TypeError: unsupported operand type(s) for *: 'NoneType' and 'NoneType'

25

问题在于图书馆的sys.exc_info()使用futures。从文档中:

此函数返回三个值的元组,这些值提供有关当前正在处理的异常的信息。[...] 如果堆栈上的任何地方都没有处理异常,则返回一个包含三个 None 值的元组。否则,返回的值为(类型、值、回溯)。它们的含义是: type 获取正在处理的异常的异常类型(一个类对象);value 获取异常参数(其关联值或 raise 的第二个参数,如果异常类型是类对象,则它始终是类实例);traceback 获取一个 traceback 对象,该对象封装了异常最初发生点的调用堆栈。

现在,如果您查看源代码,futures您可以自己了解为什么会丢失回溯:当引发异常并且仅将其设置为Future对象 时sys.exc_info()[1]通过。看:

https://code.google.com/p/pythonfutures/source/browse/concurrent/futures/thread.py (L:63) https://code.google.com/p/pythonfutures/source/browse/concurrent/期货/_base.py (L:356)

因此,为避免丢失回溯,您必须将其保存在某个地方。我的解决方法是将要提交的函数包装到一个包装器中,该包装器的唯一任务是捕获每种异常并引发一个相同类型的异常,其消息是回溯。通过这样做,当引发异常时,包装器将捕获并重新引发异常,然后将sys.exc_info()[1] 其分配给Future对象的异常时,不会丢失回溯。

于 2014-06-27T17:27:21.093 回答
15

我认为原始异常回溯在 ThreadPoolExecutor 代码中丢失了。它存储异常,然后稍后重新引发它。这是一种解决方案。您可以使用traceback模块将原始异常消息和函数f的回溯存储到字符串中。然后使用此错误消息引发异常,该消息现在包含f的行号等。运行f的代码可以包装在try ... except块中,该块捕获从 ThreadPoolExecutor 引发的异常,并打印包含原始回溯的消息。

下面的代码对我有用。我认为这个解决方案有点hacky,并且希望能够恢复原始回溯,但我不确定这是否可能。

import concurrent.futures
import sys,traceback


def f(x):
    try:
        return x * x
    except Exception, e:
        tracebackString = traceback.format_exc(e)
        raise StandardError, "\n\nError occurred. Original traceback is\n%s\n" %(tracebackString)



data = [1, 2, 3, None, 5]  # line 10

with concurrent.futures.ThreadPoolExecutor(len(data)) as executor:  # line 12
    try:
        futures = [executor.submit(f, n) for n in data]  # line 13
        for future in futures:  # line 14
           print(future.result())  # line 15
    except StandardError, e:
        print "\n"
        print e.message
        print "\n"

这在 python2.7 中给出了以下输出:

1
4
9




Error occurred. Original traceback is
Traceback (most recent call last):
File "thread.py", line 8, in f
   return x * x
TypeError: unsupported operand type(s) for *: 'NoneType' and 'NoneType'

您的原始代码在 Python 3 而不是 2.7 中运行时给出正确位置的原因是,在 Python 3 中,异常携带回溯作为属性,并且在重新引发异常时,回溯被扩展而不是被替换。下面的示例说明了这一点:

def A():
    raise BaseException("Fish")

def B():
    try:
        A()
    except BaseException as e:
        raise e

B()

我在python 2.7python 3.1中运行了这个。在 2.7 中,输出如下:

Traceback (most recent call last):
  File "exceptions.py", line 11, in <module>
    B()
  File "exceptions.py", line 9, in B
    raise e
BaseException: Fish

即异常最初是从A抛出的事实没有记录在最终输出中。当我使用python 3.1运行时,我得到了这个:

Traceback (most recent call last):
  File "exceptions.py", line 11, in <module>
    B()
  File "exceptions.py", line 9, in B
    raise e
  File "exceptions.py", line 7, in B
    A()
  File "exceptions.py", line 3, in A
    raise BaseException("Fish")
BaseException: Fish

哪个更好。如果我raise e只替换Braise中的 except 块,那么python2.7会给出完整的回溯。我的猜测是,当为python2.7反向移植这个模块时,异常传播的差异被忽略了。

于 2013-10-24T10:58:32.050 回答
5

从第一个答案中汲取灵感,这里是作为装饰者:

import functools
import traceback


def reraise_with_stack(func):

    @functools.wraps(func)
    def wrapped(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except Exception as e:
            traceback_str = traceback.format_exc(e)
            raise StandardError("Error occurred. Original traceback "
                                "is\n%s\n" % traceback_str)

    return wrapped

只需在执行的函数上应用装饰器:

@reraise_with_stack
def f():
    pass
于 2015-03-30T21:54:28.003 回答