Как в собственных исключениях можно передавать информацию о том, где именно произошла ошибка в коде?

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

Пример:


import traceback

class MyCustomError(Exception):
    def __init__(self, message, traceback_info=None):
        super().__init__(message)
        self.traceback_info = traceback_info

try:
    # Какой-то код, где может произойти ошибка
    raise ValueError("Что-то пошло не так")
except ValueError as e:
    tb = traceback.extract_stack() # or traceback.format_exc()
    raise MyCustomError("Произошла ошибка!", traceback_info=tb) from e
  

В этом примере, информация о traceback сохраняется в атрибуте traceback_info, и её можно получить при обработке исключения.


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

Вот как это можно реализовать:

  1. Импортируйте модуль traceback:
  2. import traceback
  3. Создайте собственное исключение, унаследовав его от базового класса Exception (или его подклассов, например ValueError, TypeError, если это логически соответствует ситуации):
  4. class MyCustomError(Exception):
        def __init__(self, message, filename=None, line_number=None, function_name=None):
            super().__init__(message)
            self.filename = filename
            self.line_number = line_number
            self.function_name = function_name
    
        def __str__(self):
            location_info = ""
            if self.filename and self.line_number and self.function_name:
                location_info = f" (File: {self.filename}, Line: {self.line_number}, Function: {self.function_name})"
            return f"{super().__str__()} {location_info}"
    

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

  5. В месте, где происходит ошибка, используйте traceback.extract_stack() или traceback.format_exc() чтобы получить информацию о стеке вызовов и создайте экземпляр вашего исключения:
  6. def my_function(x):
        try:
            if x < 0:
                raise ValueError("Value must be non-negative")
            result = 10 / x
            return result
        except ValueError as e:
            # Get traceback information
            trace = traceback.extract_stack()[-1]  # Get info about the current frame
            filename = trace.filename
            line_number = trace.lineno
            function_name = trace.name
    
            raise MyCustomError(str(e), filename, line_number, function_name) from e # Preserve the original exception
    
    def another_function():
        try:
            my_function(-5)
        except MyCustomError as e:
            print(f"Caught MyCustomError: {e}")
    
    another_function()
    

    В этом примере:

    • traceback.extract_stack() возвращает список фреймов стека вызовов. Мы берем последний элемент [-1], чтобы получить информацию о текущем фрейме (где возникло исключение).
    • Затем извлекаем имя файла, номер строки и имя функции из фрейма.
    • Создаем экземпляр MyCustomError, передавая туда эту информацию.
    • raise ... from e: Это важно! Это позволяет связать новое исключение (MyCustomError) с исходным исключением (ValueError). Это сохраняет информацию о первопричине ошибки, что очень полезно для отладки.

Альтернативный подход с traceback.format_exc():

Вместо traceback.extract_stack() можно использовать traceback.format_exc(), который возвращает форматированную строку с полной трассировкой стека. Однако, в этом случае вам придется парсить строку, чтобы извлечь нужную информацию, что менее удобно:

import traceback

class MyCustomError(Exception):
    def __init__(self, message, traceback_info=None):
        super().__init__(message)
        self.traceback_info = traceback_info

    def __str__(self):
        traceback_str = ""
        if self.traceback_info:
            traceback_str = f"\nTraceback:\n{self.traceback_info}"
        return f"{super().__str__()} {traceback_str}"


def my_function(x):
    try:
        if x < 0:
            raise ValueError("Value must be non-negative")
        result = 10 / x
        return result
    except ValueError as e:
        traceback_info = traceback.format_exc()
        raise MyCustomError(str(e), traceback_info) from e

def another_function():
    try:
        my_function(-5)
    except MyCustomError as e:
        print(f"Caught MyCustomError: {e}")


another_function()

В этом случае traceback_info будет содержать полную трассировку стека в виде строки, которую можно будет распечатать или проанализировать.

Преимущества и недостатки:

  • Преимущества: Значительно упрощает отладку, позволяя точно определить место возникновения ошибки.
  • Недостатки: Добавляет немного дополнительного кода. Важно не забывать про raise ... from e, чтобы не потерять информацию об исходном исключении.

Рекомендации:

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

0