← Назад к вопросам

Есть ли иерархия исключений в Python?

1.6 Junior🔥 111 комментариев
#Python Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Иерархия исключений в Python: структура и практика

Да, в Python существует полноценная иерархия исключений, которая следует принципам объектно-ориентированного программирования. Все исключения наследуются от базового класса BaseException, и эта иерархия критична для правильной обработки ошибок.

1. Структура встроенной иерархии исключений

BaseException
 ├── SystemExit
 ├── KeyboardInterrupt
 ├── GeneratorExit
 └── Exception  ← Большинство обычных исключений
     ├── StopIteration
     ├── StopAsyncIteration
     ├── ArithmeticError
     │   ├── FloatingPointError
     │   ├── OverflowError
     │   └── ZeroDivisionError
     ├── AssertionError
     ├── AttributeError
     ├── BufferError
     ├── EOFError
     ├── ImportError
     │   └── ModuleNotFoundError
     ├── LookupError
     │   ├── IndexError
     │   └── KeyError
     ├── MemoryError
     ├── NameError
     │   └── UnboundLocalError
     ├── OSError
     │   ├── BlockingIOError
     │   ├── ChildProcessError
     │   ├── ConnectionError
     │   │   ├── BrokenPipeError
     │   │   ├── ConnectionAbortedError
     │   │   ├── ConnectionRefusedError
     │   │   └── ConnectionResetError
     │   ├── FileExistsError
     │   ├── FileNotFoundError
     │   ├── InterruptedError
     │   ├── IsADirectoryError
     │   ├── NotADirectoryError
     │   ├── PermissionError
     │   ├── ProcessLookupError
     │   └── TimeoutError
     ├── RuntimeError
     │   ├── NotImplementedError
     │   └── RecursionError
     ├── SyntaxError
     │   └── IndentationError
     ├── SystemError
     ├── TypeError
     ├── ValueError
     └── Warning

Всё это наследует от Exception, а не от BaseException. Это критически важно:

# ❌ Плохо — ловит даже SystemExit и KeyboardInterrupt
try:
    some_code()
except BaseException:
    pass

# ✅ Хорошо — ловит только обычные ошибки
try:
    some_code()
except Exception:
    pass

2. Создание собственной иерархии исключений

В production коде всегда создают иерархию кастомных исключений:

# Базовое исключение приложения
class ApplicationError(Exception):
    """Базовое исключение для всего приложения"""
    pass

# Домен-специфичные исключения
class UserError(ApplicationError):
    """Базовое исключение для ошибок пользователя"""
    pass

class UserNotFoundError(UserError):
    """Пользователь не найден"""
    pass

class InvalidCredentialsError(UserError):
    """Неверные учётные данные"""
    pass

class DuplicateUserError(UserError):
    """Пользователь уже существует"""
    pass

class PaymentError(ApplicationError):
    """Базовое исключение для ошибок платежей"""
    pass

class InsufficientFundsError(PaymentError):
    """Недостаточно средств"""
    pass

class PaymentGatewayError(PaymentError):
    """Ошибка платёжного шлюза"""
    pass

3. Правильная обработка иерархии

Порядок except блоков имеет значение:

async def authenticate_user(username: str, password: str):
    try:
        user = await find_user_by_username(username)
        if not user.verify_password(password):
            raise InvalidCredentialsError(
                f"Invalid password for user {username}"
            )
        return user
    
    # Специфичные исключения ПЕРВЫМИ
    except UserNotFoundError as e:
        logger.warning(f"User not found: {username}")
        raise  # Пробросить выше
    
    except InvalidCredentialsError as e:
        logger.warning(f"Auth failed: {e}")
        raise  # Пробросить выше
    
    # Более общие исключения ПОТОМ
    except UserError as e:
        logger.error(f"User error: {e}")
        raise  # Пробросить выше
    
    # Ловля неожиданных ошибок
    except Exception as e:
        logger.exception("Unexpected error in authentication")
        raise ApplicationError("Authentication failed") from e

4. Исключения с параметрами

class ValidationError(ApplicationError):
    """Исключение валидации с деталями"""
    def __init__(self, message: str, field: str, value: any):
        self.message = message
        self.field = field
        self.value = value
        super().__init__(f"{field}: {message} (got {value})")

class BusinessLogicError(ApplicationError):
    """Ошибка бизнес-логики"""
    def __init__(self, message: str, code: str, details: dict = None):
        self.message = message
        self.code = code
        self.details = details or {}
        super().__init__(message)

# Использование
try:
    if not email.contains('@'):
        raise ValidationError(
            message="Invalid email format",
            field="email",
            value=email
        )
except ValidationError as e:
    logger.error(f"Validation failed: {e.field}")
    return {"error": e.message, "field": e.field}

5. Цепочка исключений (Exception Chaining)

Сохранение оригинальной ошибки при выбросе новой:

async def process_payment(order_id: int):
    try:
        gateway = PaymentGateway()
        result = await gateway.charge(order_id)
        return result
    
    # Преобразовать низкоуровневую ошибку в доменную
    except ConnectionError as e:
        logger.exception("Gateway connection failed")
        # ✅ Сохранить оригинальную ошибку с 'from'
        raise PaymentGatewayError(
            f"Failed to connect to payment gateway: {e}"
        ) from e
    
    except ValueError as e:
        logger.exception("Invalid payment data")
        raise ValidationError(
            message=str(e),
            field="payment_data",
            value=None
        ) from e

6. Обработка в API слое

Преобразование исключений в HTTP ответы:

from fastapi import FastAPI, HTTPException
from fastapi.responses import JSONResponse

app = FastAPI()

@app.exception_handler(UserNotFoundError)
async def user_not_found_handler(request, exc):
    return JSONResponse(
        status_code=404,
        content={"detail": "User not found", "code": "USER_NOT_FOUND"}
    )

@app.exception_handler(InvalidCredentialsError)
async def invalid_credentials_handler(request, exc):
    return JSONResponse(
        status_code=401,
        content={"detail": "Invalid credentials", "code": "INVALID_CREDENTIALS"}
    )

@app.exception_handler(InsufficientFundsError)
async def insufficient_funds_handler(request, exc):
    return JSONResponse(
        status_code=402,
        content={"detail": "Insufficient funds", "code": "INSUFFICIENT_FUNDS"}
    )

@app.exception_handler(ApplicationError)
async def application_error_handler(request, exc):
    logger.exception(f"Application error: {exc}")
    return JSONResponse(
        status_code=500,
        content={"detail": "Internal server error", "code": "INTERNAL_ERROR"}
    )

@app.post("/api/v1/users/login")
async def login(credentials: LoginRequest):
    try:
        user = await authenticate_user(
            credentials.username,
            credentials.password
        )
        return {"token": create_token(user)}
    
    except (UserNotFoundError, InvalidCredentialsError):
        raise  # Handlers выше обработают
    
    except Exception as e:
        logger.exception("Unexpected error in login")
        raise

7. Проверка типа исключения

def handle_error(exc: Exception) -> dict:
    # Проверка через isinstance
    if isinstance(exc, UserError):
        return {"type": "user_error", "message": str(exc)}
    
    elif isinstance(exc, PaymentError):
        return {"type": "payment_error", "message": str(exc)}
    
    elif isinstance(exc, ApplicationError):
        return {"type": "application_error", "message": str(exc)}
    
    else:
        return {"type": "unknown_error", "message": "Unknown error"}

# Или через type
if type(exc).__name__ == 'UserNotFoundError':
    # обработка
    pass

Критические практики:

  • Создавай свою иерархию для каждого домена (users, payments, orders)
  • Никогда не ловишь просто Exception без переброса
  • Специфичные исключения первыми в try/except блоках
  • Используй from e для сохранения оригинальной ошибки
  • Документируй какие исключения выбрасывает функция
  • Тестируй что правильные исключения выбрасываются

Правильная иерархия исключений — это залог надёжного и поддерживаемого кода.