Исключения во время исполнения: 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 без логирования, так как это скрывает проблемы.