Поймать и вывести полный трейсбек исключения в Python без остановки/выхода из программы
Я хочу перехватить и зафиксировать исключения без завершения работы программы. Например, у меня есть следующий код:
try:
do_stuff()
except Exception as err:
print(Exception, err)
# Я хочу напечатать полный трейсбек здесь,
# а не только имя исключения и его детали
Мне нужно получить точно такой же вывод, который появляется при возникновении исключения, но чтобы блок try/except не перехватывал само исключение, и я не хочу, чтобы программа завершалась. Как мне это реализовать?
5 ответ(ов)
Как напечатать полный трассировку стека без остановки программы?
Когда вы не хотите останавливаться на ошибке, необходимо обработать её с помощью конструкции try/except
:
try:
do_something_that_might_error()
except Exception as error:
handle_the_error(error)
Чтобы извлечь полную трассировку стека, мы воспользуемся модулем traceback
из стандартной библиотеки:
import traceback
И создадим достаточно сложную трассировку для демонстрации получения полного стека вызовов:
def raise_error():
raise RuntimeError('что-то пошло не так!')
def do_something_that_might_error():
raise_error()
Печать
Чтобы напечатать полную трассировку, используйте метод traceback.print_exc
:
try:
do_something_that_might_error()
except Exception as error:
traceback.print_exc()
Это выведет:
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
File "<stdin>", line 2, in do_something_that_might_error
File "<stdin>", line 2, in raise_error
RuntimeError: что-то пошло не так!
Лучше, чем печать, — логгирование:
Однако лучшей практикой будет настройка логгера для вашего модуля. Он будет знать имя модуля и сможет менять уровни (среди других атрибутов, таких как обработчики):
import logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
В этом случае, вы захотите использовать функцию logger.exception
:
try:
do_something_that_might_error()
except Exception as error:
logger.exception(error)
Что зафиксирует:
ERROR:__main__:что-то пошло не так!
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
File "<stdin>", line 2, in do_something_that_might_error
File "<stdin>", line 2, in raise_error
RuntimeError: что-то пошло не так!
Либо вы можете просто захотеть строку, в этом случае лучше воспользоваться функцией traceback.format_exc
:
try:
do_something_that_might_error()
except Exception as error:
logger.debug(traceback.format_exc())
Что зафиксирует:
DEBUG:__main__:Traceback (most recent call last):
File "<stdin>", line 2, in <module>
File "<stdin>", line 2, in do_something_that_might_error
File "<stdin>", line 2, in raise_error
RuntimeError: что-то пошло не так!
Заключение
Для всех трёх вариантов мы видим, что получаем одинаковый вывод при возникновении ошибки:
>>> do_something_that_might_error()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in do_something_that_might_error
File "<stdin>", line 2, in raise_error
RuntimeError: что-то пошло не так!
Какой вариант использовать
Проблемы с производительностью здесь не важны, так как ввод/вывод обычно преобладает. Я предпочту вариант, который делает именно то, что запрашивается, и при этом является совместимым с будущими версиями:
logger.exception(error)
Уровни и выводы логирования могут быть настроены, что делает их легко отключаемыми без изменения кода. И обычно делать то, что необходимо, — самый эффективный способ добиться результата.
В Python 3 (работает в версии 3.9) мы можем определить функцию и использовать её для вывода информации об ошибках в удобном формате. Вот пример кода:
import traceback
def get_traceback(e):
lines = traceback.format_exception(type(e), e, e.__traceback__)
return ''.join(lines)
try:
1/0
except Exception as e:
print('------Start--------')
print(get_traceback(e))
print('------End--------')
try:
spam(1,2)
except Exception as e:
print('------Start--------')
print(get_traceback(e))
print('------End--------')
В этом коде мы определяем функцию get_traceback
, которая принимает объект исключения, и возвращает строку с форматом трассировки. Мы используем эту функцию в блоках try/except
, чтобы отловить ошибки, например, деление на ноль или обращение к неопределённой переменной.
Вывод программы будет следующим:
bash-3.2$ python3 /Users/soumyabratakole/PycharmProjects/pythonProject/main.py
------Start--------
Traceback (most recent call last):
File "/Users/soumyabratakole/PycharmProjects/pythonProject/main.py", line 26, in <module>
1/0
ZeroDivisionError: division by zero
------End--------
------Start--------
Traceback (most recent call last):
File "/Users/soumyabratakole/PycharmProjects/pythonProject/main.py", line 33, in <module>
spam(1,2)
NameError: name 'spam' is not defined
------End--------
Таким образом, данный подход позволяет удобно выводить информацию о возникших исключениях, что может быть полезно при отладке кода.
Если у вас уже есть объект ошибки и вы хотите вывести всю информацию о ней, вам нужно сделать следующий несколько неудобный вызов:
import traceback
traceback.print_exception(type(err), err, err.__traceback__)
Да, print_exception
принимает три позиционных аргумента: тип исключения, сам объект исключения и внутреннюю трассировку исключения.
В Python 3.5 и более поздних версиях аргумент type(err)
является необязательным... но это позиционный аргумент, поэтому вам всё еще нужно будет явно передать None
на его месте.
traceback.print_exception(None, err, err.__traceback__)
Мне совершенно непонятно, почему всё это не сводится к простому вызову traceback.print_exception(err)
. Зачем вам вообще нужно печатать ошибку вместе с трассировкой, которая не принадлежит этой ошибке, остается для меня загадкой.
Ваш код предназначен для обработки исключений и записи их информации в строковый поток, вместо прямой отправки ошибок на стандартный вывод. Вот краткое объяснение того, что происходит в вашем коде:
- Вы импортируете необходимые модули:
io
для работы с потоками иtraceback
для получения информации об исключениях. - В блоке
try
вы вызываете функциюcall_code_that_fails()
, которая предполагается, может вызвать ошибку. - Если возникает ошибка, управление передается в блок
except
, где выполняются следующие действия:- Создается экземпляр
StringIO
, который будет использоваться для временного хранения текста ошибки. - Функция
traceback.print_exc(file=errors)
записывает информацию об исключении в созданный потокerrors
. Это позволяет избежать печати информации прямо в консоль. - Затем содержимое потока преобразуется в строку с помощью
str(errors.getvalue())
и возвращается в переменнуюcontents
. - Наконец, содержимое переменной
contents
выводится на экран с помощьюprint(contents)
. - Поток
errors
закрывается для освобождения ресурсов.
- Создается экземпляр
Важно упомянуть, что использование except:
без указания типа исключения не является хорошей практикой, так как это может скрыть неожиданные ошибки. Вместо этого лучше использовать except Exception:
для перехвата исключений более предсказуемого характера. Таким образом, ваш код может выглядеть следующим образом:
import io
import traceback
try:
call_code_that_fails()
except Exception:
errors = io.StringIO()
traceback.print_exc(file=errors)
contents = str(errors.getvalue())
print(contents)
errors.close()
Такой подход улучшает читаемость кода и делает его более надежным в обработке исключений.
Вам необходимо поместить блок try/except
внутри самого внутреннего цикла, где может произойти ошибка. Например:
for i in что-то:
for j in что-то_другое:
for k in что_угодно:
try:
что-то_сложное(i, j, k)
except Exception as e:
print(e)
try:
что-то_менее_сложное(i, j)
except Exception as e:
print(e)
И так далее.
Иными словами, вам нужно оборачивать операторы, которые могут вызвать ошибку, в блоки try/except
как можно более конкретно и как можно ближе к самому внутреннему циклу.
Вручную вызов (бросание) исключения в Python
Как напечатать исключение в Python?
Как проверить, существует ли переменная?
Как протестировать, что функция Python вызывает исключение?
Как правильно проверить, что исключение возникает в pytest?