Исключительная трассировка скрыта, если не восстать сразу
У меня есть код, похожий на этот:
import sys
def func1():
func2()
def func2():
raise Exception('test error')
def main():
err = None
try:
func1()
except:
err = sys.exc_info()[1]
pass
# some extra processing, involving checking err details (if err is not None)
# need to re-raise err so caller can do its own handling
if err:
raise err
if __name__ == '__main__':
main()
Когда func2
вызывает исключение, я получаю следующую трассировку:
Traceback (most recent call last):
File "err_test.py", line 25, in <module>
main()
File "err_test.py", line 22, in main
raise err
Exception: test error
Отсюда я не вижу, откуда исходит исключение. Исходная трассировка потеряна.
Как сохранить исходную трассировку и повторно поднять ее? Я хочу увидеть что-то похожее на это:
Traceback (most recent call last):
File "err_test.py", line 26, in <module>
main()
File "err_test.py", line 13, in main
func1()
File "err_test.py", line 4, in func1
func2()
File "err_test.py", line 7, in func2
raise Exception('test error')
Exception: test error
Ответы
Ответ 1
Пустое raise
вызывает последнее исключение.
# need to re-raise err so caller can do its own handling
if err:
raise
Если вы используете raise something
, Python не может знать, было ли исключение something
только что пойманным раньше или новое исключение с новой трассировкой стека. Вот почему существует пустой raise
, который сохраняет трассировку стека.
Ссылка здесь
Ответ 2
Можно изменить и реконструировать исключение:
Если выражения не присутствуют, raise
повторно вызывает последнее исключение, был активен в текущем объеме. Если в текущая область действия, исключение a TypeError
, указывающее, что это ошибка (если выполняется под IDLE, возникает исключение Queue.Empty
вместо этого).
В противном случае raise
оценивает выражения для получения трех объектов, используя None
как значение пропущенных выражений. Первые два объекта используется для определения типа и значения исключения.
Если присутствует третий объект, а не None
, он должен быть трассировкой объект (см. раздел Иерархия стандартного типа), и это вместо текущего местоположения вместо того, чтобы исключение. Если третий объект присутствует, а не трассировка объект или None
, возникает исключение TypeError
.
Трехмерное выражение форма raise
полезна для прозрачного воссоздания исключения в except
, но raise
без выражений должно быть предпочтительным, если исключение, которое должно быть повторно поднято, было последним активным исключением в текущей области.
Итак, если вы хотите изменить исключение и изменить его, вы можете сделать это:
try:
buggy_code_which_throws_exception()
except Exception as e:
raise Exception, "The code is buggy: %s" % e, sys.exc_info()[2]
Ответ 3
Вы можете получить много информации об исключении через sys.exc_info()
вместе с traceback module
попробуйте следующее расширение для вашего кода.
import sys
import traceback
def func1():
func2()
def func2():
raise Exception('test error')
def main():
try:
func1()
except:
exc_type, exc_value, exc_traceback = sys.exc_info()
# Do your verification using exc_value and exc_traceback
print "*** print_exception:"
traceback.print_exception(exc_type, exc_value, exc_traceback,
limit=3, file=sys.stdout)
if __name__ == '__main__':
main()
Это будет печатать, аналогично тому, что вы хотели.
*** print_exception:
Traceback (most recent call last):
File "err_test.py", line 14, in main
func1()
File "err_test.py", line 5, in func1
func2()
File "err_test.py", line 8, in func2
raise Exception('test error')
Exception: test error
Ответ 4
Ваша основная функция должна выглядеть так:
def main():
try:
func1()
except Exception, err:
# error processing
raise
Это стандартный способ обработки (и повторного поднятия) ошибок. Вот демонстрация кодекса.
Ответ 5
В то время как @Jochen ответ хорошо работает в простом случае, он не способен обрабатывать более сложные случаи, когда вы не пытаетесь перехватывать и реконструировать, но почему-то дано исключение как объект и хотите повторно бросить в совершенно новом контексте (т.е. если вам нужно обработать его в другом процессе).
В этом случае я предлагаю следующее:
- получить исходный exc_info
- форматировать исходное сообщение об ошибке со стеком трассировки
- введите новое исключение с полным сообщением об ошибке (включая трассировку стека).
Прежде чем вы это сделаете, определите новый тип исключения, который позже будет удален...
class ChildTaskException(Exception):
pass
В коде нарушения...
import sys
import traceback
try:
# do something dangerous
except:
error_type, error, tb = sys.exc_info()
error_lines = traceback.format_exception(error_type, error, tb)
error_msg = ''.join(error_lines)
# for example, if you are doing multiprocessing, you might want to send this to another process via a pipe
connection.send(error_msg)
Rethrow...
# again, a multiprocessing example of receiving that message through a pipe
error_msg = pcon.recv()
raise ChildTaskException(error_msg)