Переизбрасывание исключения в Python с сохранением трассировки стека
Я пытаюсь поймать исключение в потоке и повторно вызвать его в основном потоке:
import threading
import sys
class FailingThread(threading.Thread):
def run(self):
try:
raise ValueError('x')
except ValueError:
self.exc_info = sys.exc_info()
failingThread = FailingThread()
failingThread.start()
failingThread.join()
print(failingThread.exc_info)
raise failingThread.exc_info[1]
В общем, этот код работает и выводит следующий результат:
(<type 'exceptions.ValueError'>, ValueError('x',), <traceback object at 0x1004cc320>)
Traceback (most recent call last):
File "test.py", line 16, in <module>
raise failingThread.exc_info[1]
Однако, источником исключения указывается строка 16, где произошло повторное возбуждение. Оригинальное исключение исходит из строки 7. Как мне нужно изменить основной поток, чтобы вывод выглядел следующим образом:
Traceback (most recent call last):
File "test.py", line 7, in <module>
2 ответ(ов)
В Python 2 для поднятия исключения с использованием всех трех аргументов вы можете использовать следующий синтаксис:
raise failingThread.exc_info[0], failingThread.exc_info[1], failingThread.exc_info[2]
При передаче объекта трассировки в качестве третьего аргумента сохраняется стек вызовов.
Согласно документации help('raise')
:
Если присутствует третий объект и он не равен
None
, он должен быть объектом трассировки (см. раздел Стандартная иерархия типов), и он будет подставлен вместо текущего местоположения как место, где произошло исключение. Если третий объект присутствует и не является объектом трассировки илиNone
, будет вызвано исключениеTypeError
. Этот трехвыраженческий вариантraise
полезен для повторного поднятия исключения в блокеexcept
, однако желательно использоватьraise
без выражений, если повторно поднимаемое исключение было последним активным исключением в текущей области видимости.
В данном случае вы не можете использовать вариант без выражений.
Для Python 3 (согласно комментариям):
raise failingThread.exc_info[1].with_traceback(failingThread.exc_info[2])
Либо вы можете просто связать исключения, используя raise ... from ...
, но это вызовет цепочку исключений с оригинальным контекстом, прикрепленным в атрибуте cause, и это может быть не тем, что вам нужно.
Этот фрагмент кода работает как в Python 2, так и в Python 3:
1 try:
----> 2 raise KeyError('Сообщение об ошибке по умолчанию')
3 except KeyError as e:
4 e.args = ('Пользовательское сообщение при повторном поднятии',) # Запятая не является опечаткой, она используется для указания на то, что мы заменяем кортеж, на который указывает e.args, на другой кортеж с пользовательским сообщением.
5 raise
В этом коде мы используем try
и except
для обработки исключения KeyError
. В блоке except
мы изменяем args
исключения, присваивая ему новый кортеж с пользовательским сообщением, а затем повторно поднимаем это исключение с помощью raise
. Это позволяет нам предоставить более информативное сообщение об ошибке, когда оно будет перехвачено в другом месте в коде.
Получить описание исключения и стек вызовов, вызвавших исключение, в виде строки
Как проверить, существует ли переменная?
Правильный способ использования try/except с модулем requests в Python?
Зачем в Python нужен блок "finally"?
Есть ли разница между поднятием экземпляра класса Exception и самого класса Exception?