15

Поймать и вывести полный трейсбек исключения в Python без остановки/выхода из программы

16

Я хочу перехватить и зафиксировать исключения без завершения работы программы. Например, у меня есть следующий код:

try:
    do_stuff()
except Exception as err:
    print(Exception, err)
    # Я хочу напечатать полный трейсбек здесь,
    # а не только имя исключения и его детали

Мне нужно получить точно такой же вывод, который появляется при возникновении исключения, но чтобы блок try/except не перехватывал само исключение, и я не хочу, чтобы программа завершалась. Как мне это реализовать?

5 ответ(ов)

2

Как напечатать полный трассировку стека без остановки программы?

Когда вы не хотите останавливаться на ошибке, необходимо обработать её с помощью конструкции 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)

Уровни и выводы логирования могут быть настроены, что делает их легко отключаемыми без изменения кода. И обычно делать то, что необходимо, — самый эффективный способ добиться результата.

0

В 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--------

Таким образом, данный подход позволяет удобно выводить информацию о возникших исключениях, что может быть полезно при отладке кода.

0

Если у вас уже есть объект ошибки и вы хотите вывести всю информацию о ней, вам нужно сделать следующий несколько неудобный вызов:

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). Зачем вам вообще нужно печатать ошибку вместе с трассировкой, которая не принадлежит этой ошибке, остается для меня загадкой.

0

Ваш код предназначен для обработки исключений и записи их информации в строковый поток, вместо прямой отправки ошибок на стандартный вывод. Вот краткое объяснение того, что происходит в вашем коде:

  1. Вы импортируете необходимые модули: io для работы с потоками и traceback для получения информации об исключениях.
  2. В блоке try вы вызываете функцию call_code_that_fails(), которая предполагается, может вызвать ошибку.
  3. Если возникает ошибка, управление передается в блок 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()

Такой подход улучшает читаемость кода и делает его более надежным в обработке исключений.

0

Вам необходимо поместить блок 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 как можно более конкретно и как можно ближе к самому внутреннему циклу.

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь