Исключения во время исполнения: Python

Раздел: Ошибки -> Ошибки выполнения

Что такое ошибка времени выполнения?

Ошибка времени выполнения (runtime error) возникает, когда синтаксически корректная программа сталкивается с непредвиденной ситуацией во время исполнения. В Python такие ошибки называются исключениями. Для их обработки используется конструкция try...except.

Базовый механизм обработки исключений

Наиболее эффективное решение для предотвращения аварийного завершения программы - оборачивать потенциально опасный код в блок try и перехватывать исключения в except.


try:
    result = 10 / 0
except ZeroDivisionError:
    print('Деление на ноль перехвачено')
  

Name is not defined python (ошибка 'nameerror: name is not defined' в python)

Деление на ноль перехвачено
  

Python return error (ошибка возврата в python)

В данном примере вызов 10 / 0 генерирует ZeroDivisionError, который перехватывается и обрабатывается. Программа продолжает работу без прерывания.

Возможные проблемы:

  • Слишком общий перехват (except: без типа) может скрыть неожиданные ошибки, затрудняя отладку.
  • Отсутствие обработки специфических исключений приводит к падению программы.

Как обработать конкретное исключение, например, при вводе данных?

Наиболее частый случай - преобразование строки в число. Если пользователь вводит нечисловое значение, возникает ValueError. Для перехвата используется except ValueError:.


try:
    num = int(input('Введите число: '))
except ValueError:
    print('Ошибка: введено не число')
  

Runtime error python (ошибка времени выполнения python)

Введите число: abc
Ошибка: введено не число
  

Python exit error (ошибка выхода из python)

Можно перехватывать несколько типов исключений, перечисляя их в кортеже.


try:
    data = [1, 2, 3]
    print(data[5])
except (IndexError, TypeError) as e:
    print(f'Перехвачено: {e}')
  

System error python (системная ошибка python)

Типичные ошибки:

  • Указание неверного типа исключения - если ошибка не соответствует, она останется необработанной.
  • Порядок блоков except важен: более специфичные исключения должны быть раньше общих.

Как получить подробную информацию об ошибке?

Для анализа стека вызова используется модуль traceback. Это полезно при отладке.


import traceback

try:
    def inner():
        return 1 / 0
    inner()
except ZeroDivisionError:
    traceback.print_exc()
  
Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
  File "<stdin>", line 3, in inner
ZeroDivisionError: division by zero
  

Переменная e в except Exception as e: хранит объект исключения, от которого можно получить текст сообщения (str(e)) и тип (type(e).__name__).

Проблемы:

  • Избыточный вывод стека в продакшене может раскрыть внутреннюю структуру программы.
  • Игнорирование исключения (except: pass) без логирования затрудняет обнаружение ошибок.

Как гарантировать освобождение ресурсов (файлы, соединения)?

Блок finally выполняется всегда, независимо от того, возникло исключение или нет. Это подходит для закрытия файлов, сетевых сокетов.


file = None
try:
    file = open('test.txt', 'r')
    content = file.read()
except FileNotFoundError:
    print('Файл не найден')
finally:
    if file:
        file.close()
        print('Файл закрыт')
  
Файл не найден
Файл закрыт
  

Современный подход - использование менеджера контекста (with), который автоматически закрывает ресурс.


try:
    with open('test.txt', 'r') as f:
        data = f.read()
except FileNotFoundError:
    print('Файл не найден')
  

Проблемы:

  • Забытый вызов close() приводит к утечке ресурсов.
  • Использование finally с исключениями внутри него может маскировать исходную ошибку.

Как создать собственное исключение для специфических ситуаций?

Пользовательские исключения наследуются от Exception и позволяют гибко обрабатывать бизнес-логику.


class NegativeValueError(Exception):
    pass

def withdraw(amount):
    if amount < 0:
        raise NegativeValueError('Сумма не может быть отрицательной')
    return amount

try:
    withdraw(-100)
except NegativeValueError as e:
    print(f'Ошибка: {e}')
  
Ошибка: Сумма не может быть отрицательной
  

Типичные ошибки:

  • Наследование от базового Exception без переопределения - все равно работает, но теряется информативность.
  • Недостаточная иерархия исключений ведет к путанице при обработке.

Как выполнить код, только если исключение не возникло?

Блок else в конструкции try...except...else выполняется, когда исключения не было. Это улучшает читаемость.


try:
    number = int(input('Число: '))
except ValueError:
    print('Не число')
else:
    print(f'Квадрат числа: {number ** 2}')
  
Число: 5
Квадрат числа: 25
  

Проблемы:

  • Код в else не должен содержать дополнительных операций, способных вызвать то же исключение - иначе оно останется необработанным.
  • Путаница между else и finally: else запускается только при отсутствии исключения, finally - всегда.

Расширенные примеры обработки ошибок выполнения

Пример 1. Вложенные блоки try-except

Обработка исключений на разных уровнях вложенности позволяет изолировать проблемные участки.

Пример

import sys

def parse_int(s):
    try:
        return int(s)
    except ValueError:
        print('Внутренняя ошибка преобразования', file=sys.stderr)
        return None

def main():
    user_input = input('Введите число: ')
    try:
        result = parse_int(user_input)
        if result is not None:
            print('Результат:', 10 / result)
        else:
            print('Не удалось обработать входные данные')
    except ZeroDivisionError:
        print('Деление на ноль')

if __name__ == '__main__':
    main()
Введите число: 0
Результат: 10 / 0
Внутренняя ошибка преобразования? Нет, ошибка ZeroDivisionError перехвачена в main
Вывод: Деление на ноль

Пояснение: внутренний try перехватывает ValueError, внешний - ZeroDivisionError. Каждый блок обрабатывает только свою группу исключений.

Пример 2. Перехват нескольких исключений с одним действием

Использование кортежа типов позволяет объединить обработку родственных ошибок.

Пример

try:
    value = {'a': 1}['b']
    result = 10 + 'abc'
except (KeyError, TypeError) as e:
    print(f'Ошибка доступа или типа: {type(e).__name__}: {e}')
Ошибка доступа или типа: KeyError: 'b'

Важно: порядок исключений в кортеже не имеет значения, но логически их группируют по смыслу.

Пример 3. Повторное возбуждение исключения с дополнительной информацией

Иногда нужно перехватить исключение, добавить контекст и снова его возбудить.

Пример

import math

def sqrt_ex(x):
    try:
        return math.sqrt(x)
    except ValueError as e:
        raise RuntimeError('Ошибка при извлечении корня') from e

try:
    sqrt_ex(-4)
except RuntimeError as e:
    print('Основная ошибка:', e)
    print('Исходная ошибка:', e.__cause__)
Основная ошибка: Ошибка при извлечении корня
Исходная ошибка: math domain error

Ключевое слово from связывает цепочку исключений, что упрощает отладку.

Пример 4. Пользовательское исключение с атрибутами

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

Пример

class BadRequest(Exception):
    def __init__(self, message, status_code=400):
        super().__init__(message)
        self.status_code = status_code

def process_request(data):
    if not isinstance(data, dict):
        raise BadRequest('Данные должны быть словарём', status_code=422)
    return data.get('name')

try:
    process_request('string')
except BadRequest as e:
    print(f'Ошибка {e.status_code}: {e}')
Ошибка 422: Данные должны быть словарём

Такие исключения часто применяются в веб-фреймворках для передачи HTTP-статусов.

Пример 5. Обработка AssertionError и отладка

Утверждения (assert) генерируют AssertionError при ложном условии. Их можно перехватывать, хотя обычно они служат для отладки.

Пример

def divide(a, b):
    assert b != 0, 'Делитель не должен быть нулём'
    return a / b

try:
    result = divide(10, 0)
except AssertionError as e:
    print('Утверждение не выполнено:', e)
Утверждение не выполнено: Делитель не должен быть нулём

В продакшене утверждения могут быть отключены флагом -O, поэтому полагаться на них для обработки ошибок не следует.

Пример 6. Индексация и ключи: IndexError и KeyError

Работа с последовательностями и словарями часто сопровождается этими исключениями.

Пример

items = ['a', 'b', 'c']
map_data = {'x': 10, 'y': 20}

try:
    print(items[5])
except IndexError:
    print('Индекс вне диапазона')

try:
    print(map_data['z'])
except KeyError:
    print('Ключ отсутствует')
Индекс вне диапазона
Ключ отсутствует

Для безопасного доступа лучше использовать методы .get() у словарей и проверять длину списков, но в критических местах обработка исключений приемлема.

Пример 7. Контекстный менеджер как альтернатива try-finally

Конструкция with автоматически вызывает __enter__ и __exit__, что гарантирует освобождение ресурсов.

Пример

class ManagedFile:
    def __init__(self, name):
        self.name = name
    def __enter__(self):
        self.file = open(self.name, 'w')
        return self.file
    def __exit__(self, exc_type, exc_val, exc_tb):
        if self.file:
            self.file.close()
        print(f'Файл {self.name} закрыт')

with ManagedFile('test.txt') as f:
    f.write('Привет, мир!')
Файл test.txt закрыт

Это надежнее ручного finally, так как код становится чище и исключает риск забыть закрыть ресурс.

Пример 8. Игнорирование исключения с предупреждением

В некоторых ситуациях ошибку можно проигнорировать, но следует хотя бы залогировать её.

Пример

import logging

logging.basicConfig(level=logging.WARNING)

try:
    risky_operation()
except SomeExpectedError:
    logging.warning('Несущественная ошибка, продолжаем')

Не рекомендуется использовать пустой except: pass без логирования, так как это скрывает проблемы.

Ошибка времени выполнения Python - comments

En
Runtime error python (python)