2013-10-11 2 views
17

Пример использования concurrent.futures (Backport для 2.7):Получение оригинальный номер строки для исключения в concurrent.futures

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? И есть ли какое-нибудь обходное решение?

+0

Я также искал ответ на этот вопрос. Благодаря! – drpoo

ответ

9

Я думаю, что исходный след отслеживания теряется в коде ThreadPoolExecutor. Он хранит исключение, а затем повторно создает его позже. Вот одно решение. Вы можете использовать модуль traceback, чтобы сохранить исходное сообщение об исключении и трассировку из вашей функции f в строку. Затем создайте исключение с этим сообщением об ошибке, которое теперь содержит номер строки и т. Д. f. Код, который запускается f может быть обернут в try ... за исключением блок, который улавливает исключение, поднятое из ThreadPoolExecutor, и печатает сообщение, содержащее исходную трассировку.

Приведенный ниже код работает для меня. Я думаю, что это решение немного взломано и предпочитает восстанавливать исходную трассу, но я не уверен, что это возможно.

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.7 и питона 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 

т.е. тот факт, что исключение первоначально был брошен из не записывается в конечном итоге вывод. Когда я бегу с питона 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 только raise в исключенном блоке в B, то python2.7 дает полную трассировку. Я предполагаю, что при обратном переносе этого модуля на python2.7 различия в распространении исключений были упущены.

+0

Благодарим вас за подробное объяснение и извините за поздний прием. – djeendo

14

Я был в вашей ситуации, и мне действительно нужно было отследить возвышенные исключения. Мне удалось разработать это обходное решение, которое заключается в использовании следующего подкласса 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 получает тип исключения обрабатываемого исключения (объект класса); значение получает исключение параметр (его связанное значение или второй аргумент для повышения, который всегда является экземпляром класса , если тип исключения является объектом класса); 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, трассировка не теряется.

2

Принимая вдохновение из первого ответа, вот он как декоратор:

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 

 Смежные вопросы

  • Нет связанных вопросов^_^