2016-09-23 1 views
1

Имея поднятый 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)) 

который Unsurprisingly приводит к:

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__.

ответ

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') 

При необходимости повторять, пока не дойдете до целевого контекста/TRACEBACK желаемого.


Теперь в случае многопроцессорной, который я не знал, сделал бы это большая разница, так как вопрос подразумевает что-то общее (Как я могу «прыгать» в StackFrame с исключением?), Но оказывается, Спецификация в multiprocessing сделала все различие.

В Python 3.4 было сделано обходное решение, чтобы просто показать эту трассировку как строку; из-за того, сколько материала имеет на самом деле трассировка, передавая все, что оказалось трудным, как обсуждалось в issue 13831 на трекере Python, поэтому вместо этого был взломан взломанный атрибут __cause__ в текущее исключение, но он не является полным __traceback__ as у него просто есть строковое представление этого, как я подозревал.

Во всяком случае, это то, что случилось бы:

(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__ 

Так что это на самом деле не возможно, пока они не выяснить, как объединить все эти состояния через границы процессов.

+0

Благодарим вас за ответ. Я попробовал это с моим примером, но 'self._value .__ context__' является' None'. Это связано с тем, что исключение было поднято вместо ререйза (поднять без аргументов)? –

+0

О, хм, удаленная трассировка выглядит как строка, но, учитывая то, что вы получили (исключение - 'self._value'), вы можете просто сделать это с помощью' self._value .__ traceback__'. Извините за то, что я не сделал свой ответ немного более ясным. – metatoaster

+0

Я пробовал это. Однако он загружает фрейм с 'raise self._value' в основном процессе. Я думаю, это ограничение из-за многопроцессорности. Я определенно узнал что-то из вашего ответа и примет его, если кто-то не найдет способ для моей проблемы в течение следующего дня. –

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