12. Обработка ошибок

try / except / else / finally
Заголовок раздела «try / except / else / finally»try: result = 10 / 0except ZeroDivisionError: print("Деление на ноль!")
# Несколько excepttry: data = int(input("Введи число: ")) result = 100 / dataexcept ValueError: print("Это не число!")except ZeroDivisionError: print("Не делим на ноль!")except Exception as e: # поймать всё print(f"Неизвестная ошибка: {e}")else: print(f"Результат: {result}") # если не было исключенийfinally: print("Это выполнится всегда") # cleanup
# Несколько в одном excepttry: ...except (ValueError, TypeError) as e: print(f"Ошибка данных: {e}")Иерархия исключений
Заголовок раздела «Иерархия исключений»# Основные встроенные исключения:# Exception# ├── ValueError — неверное значение# ├── TypeError — неверный тип# ├── KeyError — ключ не найден в словаре# ├── IndexError — индекс вне диапазона# ├── AttributeError — объект не имеет атрибута# ├── FileNotFoundError — файл не найден# ├── PermissionError — нет доступа# ├── ZeroDivisionError — деление на ноль# ├── ImportError — ошибка импорта# ├── RuntimeError — общая ошибка выполнения# └── StopIteration — конец итерации
# Ловить базовый класс — поймает все наследниковtry: open("nonexistent.txt")except OSError as e: # FileNotFoundError, PermissionError и др. print(f"Ошибка ФС: {e}")Создание своих исключений
Заголовок раздела «Создание своих исключений»# Базовое исключение для модуляclass AppError(Exception): """Базовое исключение приложения.""" pass
class ValidationError(AppError): """Ошибка валидации данных.""" def __init__(self, field: str, message: str): self.field = field self.message = message super().__init__(f"Ошибка в поле '{field}': {message}")
class NotFoundError(AppError): """Объект не найден.""" def __init__(self, resource: str, id: int | str): super().__init__(f"{resource} с id={id} не найден") self.resource = resource self.id = id
class AuthError(AppError): """Ошибка аутентификации.""" pass
# Использованиеdef get_user(id: int) -> dict: users = {1: {"name": "Яша"}} if id not in users: raise NotFoundError("User", id) return users[id]
def validate_age(age: int) -> None: if age < 0: raise ValidationError("age", "не может быть отрицательным") if age > 150: raise ValidationError("age", "слишком большое значение")
try: user = get_user(999)except NotFoundError as e: print(f"404: {e}") # 404: User с id=999 не найденraise и reraise
Заголовок раздела «raise и reraise»def process_payment(amount: float) -> None: if amount <= 0: raise ValueError(f"Сумма должна быть положительной, получено: {amount}")
# Re-raise — поймать, добавить контекст, переброситьdef transfer_money(from_account: str, to_account: str, amount: float): try: process_payment(amount) except ValueError as e: raise RuntimeError(f"Ошибка перевода {from_account} → {to_account}") from e
# Поймать и проверить цепочку ошибокtry: transfer_money("acc1", "acc2", -100)except RuntimeError as e: print(f"Основная ошибка: {e}") print(f"Причина: {e.__cause__}")Практический паттерн: Result type
Заголовок раздела «Практический паттерн: Result type»from typing import TypeVar, Genericfrom dataclasses import dataclass
T = TypeVar("T")E = TypeVar("E", bound=Exception)
@dataclassclass Ok(Generic[T]): value: T
@property def is_ok(self) -> bool: return True
@dataclassclass Err(Generic[E]): error: E
@property def is_ok(self) -> bool: return False
Result = Ok[T] | Err[Exception]
def safe_divide(a: float, b: float) -> Result: try: return Ok(a / b) except ZeroDivisionError as e: return Err(e)
result = safe_divide(10, 0)if result.is_ok: print(f"Результат: {result.value}")else: print(f"Ошибка: {result.error}")Логирование вместо print
Заголовок раздела «Логирование вместо print»import logging
# Настройка базового логированияlogging.basicConfig( level=logging.DEBUG, format="%(asctime)s [%(levelname)s] %(name)s: %(message)s", handlers=[ logging.FileHandler("app.log"), logging.StreamHandler() # консоль ])
logger = logging.getLogger(__name__)
def process_data(data: dict) -> None: logger.debug(f"Начало обработки: {data}")
try: result = expensive_operation(data) logger.info(f"Обработано успешно: {result}") except ValueError as e: logger.warning(f"Некорректные данные: {e}") except Exception as e: logger.error(f"Критическая ошибка: {e}", exc_info=True) raise
# Использование loguru (лучше для новых проектов)# pip install logurufrom loguru import logger
logger.add("app.log", rotation="1 day", retention="7 days")logger.add("errors.log", level="ERROR")
logger.info("Сервер запущен на порту {port}", port=8000)logger.error("Ошибка подключения к БД")Обработка ошибок в FastAPI
Заголовок раздела «Обработка ошибок в FastAPI»from fastapi import FastAPI, HTTPException, Requestfrom fastapi.responses import JSONResponse
app = FastAPI()
# Кастомное исключениеclass ItemNotFoundError(Exception): def __init__(self, item_id: int): self.item_id = item_id
# Регистрируем обработчик@app.exception_handler(ItemNotFoundError)async def item_not_found_handler(request: Request, exc: ItemNotFoundError): return JSONResponse( status_code=404, content={"detail": f"Item {exc.item_id} not found"} )
@app.get("/items/{item_id}")async def get_item(item_id: int): items = {1: "Apple", 2: "Banana"}
if item_id not in items: raise ItemNotFoundError(item_id) # наш exception
return {"id": item_id, "name": items[item_id]}
# HTTPException — стандарт FastAPI@app.delete("/items/{item_id}")async def delete_item(item_id: int, user_role: str): if user_role != "admin": raise HTTPException( status_code=403, detail="Только администраторы могут удалять элементы" ) return {"deleted": item_id}Задание
Заголовок раздела «Задание»# Задание 1: Иерархия ошибок для интернет-магазина# Создай:# - ShopError (базовый)# - ProductNotFoundError(product_id)# - OutOfStockError(product_name, requested, available)# - PaymentError(amount, reason)# - InvalidCouponError(code)
# Задание 2: Безопасный JSON парсерdef safe_parse_json(text: str) -> dict | None: """Возвращает None вместо исключения при ошибке.""" pass
# Задание 3: Retry декораторdef retry(max_attempts: int = 3, exceptions=(Exception,)): """ Декоратор для повторных попыток при ошибке. Пример: @retry(max_attempts=3, exceptions=(ConnectionError,)) """ passВ следующем уроке — Async/Await!