Исключения в языке Python: механизмы и практические примеры
Основы обработки исключений
Исключения в Python позволяют реагировать на ошибки во время выполнения программы, не прерывая её полностью. Они представляют собой объекты, которые наследуются от базового класса BaseException. Для обработки используется конструкция try / except.
Базовый механизм try except
Основное решение для перехвата исключений: блок кода, который может вызвать ошибку, помещается внутрь try, а обработчик ошибки записывается в except. Если исключение не возникает, блок except пропускается.
try:
number = int(input("Введите число: "))
result = 10 / number
print("Результат:", result)
except ZeroDivisionError:
print("Деление на ноль не допускается")
except ValueError:
print("Введено не число")
Python ignore error (игнорирование ошибок python)
В этом примере перехватываются два конкретных типа исключений: ZeroDivisionError и ValueError. Если возникает любое другое исключение, программа завершится с ошибкой.
Почему не стоит перехватывать все исключения без указания типа?
Использование except: без указания типа перехватывает все исключения, включая SystemExit и KeyboardInterrupt, что может скрыть критические ошибки и затруднить отладку. Рекомендуется всегда указывать конкретные классы исключений.
Как обработать несколько разных исключений в одном блоке except?
Можно перечислить несколько классов исключений в кортеже:
try:
value = int(input())
result = 100 / value
except (ValueError, ZeroDivisionError) as e:
print(f"Произошла ошибка: {e}")
Python exception (исключения в python)
Это удобно, когда обработка для разных типов ошибок одинаковая.
Как выполнить код, если исключения не было (блок else)?
Блок else выполняется только если в try не возникло исключений. Это позволяет отделить успешный сценарий от обработки ошибок.
try:
number = int(input("Введите число: "))
result = 10 / number
except ZeroDivisionError:
print("На ноль делить нельзя")
except ValueError:
print("Нужно ввести число")
else:
print(f"Результат: {result}")
исключения в python инструкция try except (обработка исключений try except в python)
Как гарантировать выполнение кода после try, независимо от ошибки (finally)?
Блок finally выполняется всегда: и после успешного завершения try, и после обработки except, и даже если исключение не было перехвачено. Обычно используется для освобождения ресурсов (закрытие файла, соединения).
try:
file = open("data.txt", "r")
content = file.read()
except FileNotFoundError:
print("Файл не найден")
finally:
file.close() # выполнится в любом случае
Python errno (обработка ошибки errno в python)
Однако при работе с файлами чаще применяется конструкция with (контекстный менеджер), которая сама закрывает файл.
Как повторно возбудить исключение (raise)?
Иногда в обработчике нужно частично обработать ошибку и передать её дальше по стеку вызовов. Для этого используется raise без аргументов.
def divide(a, b):
try:
return a / b
except ZeroDivisionError:
print("Попытка деления на ноль, пробрасываем дальше")
raise
try:
divide(10, 0)
except ZeroDivisionError:
print("Обработано выше")
Python get traceback (получение трассировки стека в python)
Попытка деления на ноль, пробрасываем дальше Обработано выше
Python except print (обработка исключений с выводом в python)
Как создать собственное исключение?
Достаточно определить класс, наследующий от Exception (или его подкласса). Можно добавить атрибуты для хранения дополнительной информации.
class NegativeValueError(Exception):
"""Исключение для отрицательных значений"""
def __init__(self, value, message="Значение не может быть отрицательным"):
self.value = value
self.message = message
super().__init__(self.message)
def sqrt_positive(x):
if x < 0:
raise NegativeValueError(x)
return x ** 0.5
try:
sqrt_positive(-5)
except NegativeValueError as e:
print(f"Ошибка: {e.message}, получено значение {e.value}")
Ошибка: Значение не может быть отрицательным, получено значение -5
Как обработать исключения при работе с ресурсами (контекстный менеджер with)?
Контекстный менеджер автоматически вызывает методы __enter__ и __exit__, что гарантирует освобождение ресурсов даже при возникновении исключения.
class ManagedFile:
def __init__(self, filename):
self.filename = filename
def __enter__(self):
self.file = open(self.filename, "r")
return self.file
def __exit__(self, exc_type, exc_value, traceback):
if self.file:
self.file.close()
# Если нужно подавить исключение - вернуть True
return False
with ManagedFile("data.txt") as f:
print(f.read())
Встроенный open уже является контекстным менеджером, поэтому для файлов достаточно with open(...) as f:.
Какая типичная ошибка возникает при использовании вложенных try except?
Если во внутреннем блоке except перехватывается исключение, которое уже было перехвачено на уровне выше, это может привести к путанице. Рекомендуется избегать глубокой вложенности и использовать отдельные функции для обработки.
Расширенные примеры обработки исключений
1. Повторные попытки выполнения (retry)
Иногда полезно повторить операцию несколько раз при возникновении временной ошибки (например, сетевой).
import time
from random import random
def unstable_operation():
if random() < 0.7:
raise ConnectionError("Временная ошибка соединения")
return "Успех"
def retry(func, max_attempts=3, delay=1):
last_exception = None
for attempt in range(1, max_attempts + 1):
try:
return func()
except ConnectionError as e:
print(f"Попытка {attempt} не удалась: {e}")
last_exception = e
if attempt < max_attempts:
time.sleep(delay)
raise RuntimeError("Все попытки исчерпаны") from last_exception
try:
result = retry(unstable_operation, max_attempts=5, delay=0.5)
print("Результат:", result)
except RuntimeError as e:
print("Итоговое исключение:", e)
Попытка 1 не удалась: Временная ошибка соединения Попытка 2 не удалась: Временная ошибка соединения Попытка 3 не удалась: Временная ошибка соединения Попытка 4 не удалась: Временная ошибка соединения Попытка 5 не удалась: Временная ошибка соединения Итоговое исключение: Все попытки исчерпаны
2. Цепочка исключений (raise ... from)
Позволяет связать новое исключение с исходным, чтобы сохранить контекст ошибки.
def parse_number(s):
try:
return int(s)
except ValueError as e:
raise ValueError("Не удалось преобразовать строку в число") from e
try:
parse_number("abc")
except ValueError as e:
print("Ошибка:", e)
print("Исходная причина:", e.__cause__)
Ошибка: Не удалось преобразовать строку в число Исходная причина: invalid literal for int() with base 10: 'abc'
3. Логирование исключений с сохранением стека вызовов
Модуль logging предоставляет метод exception, который автоматически добавляет traceback.
import logging
logging.basicConfig(level=logging.ERROR, format='%(asctime)s - %(levelname)s - %(message)s')
def risky_function():
return 1 / 0
try:
risky_function()
except ZeroDivisionError:
logging.exception("Произошло деление на ноль")
2024-01-15 10:30:00,000 - ERROR - Произошло деление на ноль
Traceback (most recent call last):
File "<ipython-input-...>", line 8, in <module>
risky_function()
File "<ipython-input-...>", line 5, in risky_function
return 1 / 0
ZeroDivisionError: division by zero
4. Пользовательское исключение с несколькими атрибутами и кастомным сообщением
class ValidationError(Exception):
def __init__(self, field, value, message=None):
self.field = field
self.value = value
self.message = message or f"Некорректное значение в поле '{field}': {value}"
super().__init__(self.message)
def validate_age(age):
if age < 0 or age > 150:
raise ValidationError("age", age)
if not isinstance(age, int):
raise ValidationError("age", age, "Возраст должен быть целым числом")
return True
try:
validate_age(-5)
except ValidationError as e:
print(f"Поле: {e.field}, значение: {e.value}")
print(e)
Поле: age, значение: -5 Некорректное значение в поле 'age': -5
5. Обработка исключений в асинхронном коде (asyncio)
В асинхронных функциях (coroutine) исключения перехватываются так же, как в обычных, но требуется await.
import asyncio
async def fetch_data(url):
if "bad" in url:
raise ValueError("Плохой URL")
return f"Данные из {url}"
async def main():
urls = ["good_url", "bad_url", "another_good"]
tasks = [fetch_data(url) for url in urls]
for coro in asyncio.as_completed(tasks):
try:
result = await coro
print(result)
except ValueError as e:
print(f"Исключение: {e}")
asyncio.run(main())
Данные из good_url Исключение: Плохой URL Данные из another_good
6. Использование else с finally для точного контроля потока
def safe_divide(a, b):
try:
result = a / b
except ZeroDivisionError:
print("Деление на ноль")
result = None
else:
print("Деление успешно")
finally:
print("Блок finally выполнен")
return result
print(safe_divide(10, 2))
print(safe_divide(10, 0))
Деление успешно Блок finally выполнен 5.0 Деление на ноль Блок finally выполнен None
7. Получение информации об исключении через sys.exc_info
import sys
try:
[1, 2, 3][10]
except IndexError:
exc_type, exc_value, exc_traceback = sys.exc_info()
print(f"Тип: {exc_type.__name__}")
print(f"Сообщение: {exc_value}")
print(f"Строка с ошибкой: {exc_traceback.tb_lineno}")
Тип: IndexError Сообщение: list index out of range Строка с ошибкой: 4