Исключительная трассировка скрыта, если не была повторно вызвана немедленно - PullRequest
63 голосов
/ 28 января 2011

У меня есть фрагмент кода, похожий на этот:

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

Ответы [ 5 ]

108 голосов
/ 28 января 2011

Пробел raise вызывает последнее исключение.

# need to re-raise err so caller can do its own handling
if err:
    raise

Если вы используете raise something Python не может узнать, было ли something исключение, только что перехваченное ранее, или новое исключение сновая трассировка стека.Вот почему есть пробел raise, который сохраняет трассировку стека.

Ссылка здесь

61 голосов
/ 04 января 2013

Можно изменить и перебросить исключение:

Если выражения отсутствуют, raise повторно вызывает последнее исключение, которое был активен в текущей области. Если в текущая область, возникает исключение TypeError, указывающее, что это ошибка (если работает под IDLE, возникает исключение Queue.Empty вместо этого).

В противном случае raise вычисляет выражения для получения трех объектов, используя None как значение пропущенных выражений. Первые два объекта используется для определения типа и значения исключения.

Если присутствует третий объект, а не None, это должно быть отслеживание объект (see section Стандартная иерархия типов), и это заменить вместо текущего местоположения в качестве места, где исключение произошло. Если третий объект присутствует, а не отслеживание объект или 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]
5 голосов
/ 28 января 2011

Вы можете получить много информации об исключении через sys.exc_info() вместе с модулем traceback

, попробуйте следующее расширение вашего кода.

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 голосов
/ 11 апреля 2017

Хотя ответ @ Jochen хорошо работает в простом случае, он не способен обрабатывать более сложные случаи, когда вы не ловите напрямую и не перебрасываете, но по какой-то причине получаете исключение как объект и хотите переброситьв совершенно новом контексте (т. е. если вам нужно обработать его в другом процессе).

В этом случае я предлагаю следующее:

  1. получить оригинальную exc_info
  2. отформатируйте исходное сообщение об ошибке с помощью трассировки стека
  3. сгенерируйте новое исключение с этим полным сообщением об ошибке (включая трассировку стека)

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

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)
2 голосов
/ 28 января 2011

Ваша основная функция должна выглядеть так:

def main():
    try:
        func1()
    except Exception, err:
        # error processing
        raise

Это стандартный способ обработки (и повторного вызова) ошибок. Вот демонстрация кодовой панели.

...