Комментарии (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для сохранения оригинальной ошибки - Документируй какие исключения выбрасывает функция
- Тестируй что правильные исключения выбрасываются
Правильная иерархия исключений — это залог надёжного и поддерживаемого кода.