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ドキュメントから:

この関数は、現在処理されている例外に関する情報を提供する 3 つの値のタプルを返します。[...] スタックのどこにも例外が処理されていない場合、3 つの None 値を含むタプルが返されます。それ以外の場合、返される値は (タイプ、値、トレースバック) です。その意味は次のとおりです。 type は、処理される例外の例外タイプ (クラス オブジェクト) を取得します。value は例外パラメーター (関連する値または raise の 2 番目の引数。例外の型がクラス オブジェクトの場合は常にクラス インスタンス) を取得します。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/ futures/_base.py (L:356)

したがって、トレースバックが失われないようにするには、どこかに保存する必要があります。私の回避策は、サブミットする関数をラッパーにラップすることです。その唯一のタスクは、あらゆる種類の例外をキャッチし、メッセージがトレースバックである同じタイプの例外を発生させることです。これを行うことで、例外が発生したときにラッパーによってキャプチャされて再発生さsys.exc_info()[1] れ、オブジェクトの例外に割り当てられたときFutureにトレースバックが失われません。

于 2014-06-27T17:27:21.093 に答える
15

元の例外のトレースバックが ThreadPoolExecutor コードで失われていると思います。例外を保存し、後で再発生させます。これが1つの解決策です。tracebackモジュールを使用して、関数fからの元の例外メッセージとトレースバックを文字列に格納できます。次に、このエラー メッセージで例外を発生させます。このエラー メッセージには、 fの行番号などが含まれています。fを実行するコードは、ThreadPoolExecutor から発生した例外をキャッチし、元のトレースバックを含むメッセージを出力するtry ... exceptブロックでラップできます。

以下のコードは私にとってはうまくいきます。この解決策は少しハックで、元のトレースバックを回復できることを望んでいると思いますが、それが可能かどうかはわかりません。

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'

2.7 ではなく Python 3 で実行したときに元のコードが正しい場所を示す理由は、Python 3 の例外ではトレースバックが属性として保持され、例外を再発生させると、トレースバックが置き換えられるのではなく拡張されるためです。以下の例は、これを示しています。

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

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

B()

これをpython 2.7およびpython 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

どちらが良いですか。Bの except ブロックraise eだけを置き換えると、python2.7は完全なトレースバックを返します。私の推測では、このモジュールをpython2.7にバックポートしたときに、例外の伝播の違いが見落とされていました。raise

于 2013-10-24T10:58:32.050 に答える