Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Создание собственных исключений в Python
Пользовательские исключения — это один из инструментов для построения чистого и поддерживаемого кода. Они позволяют явно обработать специфичные для вашего приложения ошибочные ситуации.
1. Базовое пользовательское исключение
Самый простой способ — наследование от Exception:
class CustomError(Exception):
pass
# Использование
try:
raise CustomError("Что-то пошло не так")
except CustomError as e:
print(f"Поймано исключение: {e}")
2. Исключение с собственными атрибутами
class ValidationError(Exception):
def __init__(self, message: str, field: str, value: any):
self.message = message
self.field = field
self.value = value
super().__init__(self.message)
def __str__(self):
return f"Ошибка валидации в поле '{self.field}': {self.message}"
# Использование
try:
raise ValidationError(
message="Email должен содержать @",
field="email",
value="invalid-email"
)
except ValidationError as e:
print(e)
print(f"Поле: {e.field}, Значение: {e.value}")
Вывод:
Ошибка валидации в поле 'email': Email должен содержать @
Поле: email, Значение: invalid-email
3. Иерархия исключений
Создайте базовое исключение для всего приложения:
# Базовое исключение для всего приложения
class AppError(Exception):
pass
# Специфичные исключения
class ValidationError(AppError):
pass
class NotFoundError(AppError):
pass
class AuthenticationError(AppError):
pass
class PermissionError(AppError):
pass
# Использование
try:
user = find_user_by_id(999)
if not user:
raise NotFoundError("Пользователь не найден")
except NotFoundError as e:
print(f"404: {e}")
except AppError as e:
print(f"Общая ошибка приложения: {e}")
4. Исключение с дополнительным контекстом
class APIError(Exception):
def __init__(self, message: str, status_code: int, response_body: dict = None):
self.message = message
self.status_code = status_code
self.response_body = response_body or {}
super().__init__(self.message)
def to_dict(self):
return {
'error': self.message,
'status': self.status_code,
'details': self.response_body
}
# Использование в API обработчике
def get_user_from_api(user_id: int):
try:
response = requests.get(f'https://api.example.com/users/{user_id}')
if response.status_code == 404:
raise APIError(
message="User not found",
status_code=404,
response_body={'user_id': user_id}
)
except APIError as e:
return {"error": e.to_dict()}
5. Исключение с кастомной функцией str
class DatabaseError(Exception):
def __init__(self, operation: str, table: str, reason: str):
self.operation = operation
self.table = table
self.reason = reason
super().__init__()
def __str__(self):
return f"Database error: {self.operation} in table '{self.table}' - {self.reason}"
def __repr__(self):
return f"DatabaseError(operation='{self.operation}', table='{self.table}', reason='{self.reason}')"
# Использование
try:
raise DatabaseError(
operation='INSERT',
table='users',
reason='Duplicate key violation'
)
except DatabaseError as e:
print(str(e)) # Database error: INSERT in table 'users' - Duplicate key violation
print(repr(e)) # DatabaseError(operation='INSERT', table='users', reason='Duplicate key violation')
6. Исключение с поддержкой цепочки ошибок
class ProcessingError(Exception):
def __init__(self, message: str, original_error: Exception = None):
self.message = message
self.original_error = original_error
super().__init__(self.message)
# Использование
def process_file(filename: str):
try:
with open(filename, 'r') as f:
data = json.load(f)
except FileNotFoundError as e:
raise ProcessingError(
message=f"Не удалось открыть файл {filename}",
original_error=e
)
except json.JSONDecodeError as e:
raise ProcessingError(
message=f"Некорректный JSON в файле {filename}",
original_error=e
)
try:
process_file('data.json')
except ProcessingError as e:
print(f"Ошибка обработки: {e.message}")
print(f"Оригинальная ошибка: {e.original_error}")
7. Современный подход: использование raise ... from ...
class ConfigError(Exception):
pass
def load_config(config_file: str):
try:
with open(config_file) as f:
return json.load(f)
except FileNotFoundError as e:
raise ConfigError(f"Config file not found: {config_file}") from e
except json.JSONDecodeError as e:
raise ConfigError(f"Invalid JSON in config: {config_file}") from e
# Это сохраняет оригинальный stack trace
try:
load_config('missing.json')
except ConfigError as e:
print(e) # Config file not found: missing.json
print(e.__cause__) # Оригинальная FileNotFoundError
8. Исключение для бизнес-логики
class InsufficientFundsError(Exception):
def __init__(self, required: float, available: float):
self.required = required
self.available = available
super().__init__(f"Недостаточно средств: требуется {required}, доступно {available}")
class InvalidTransactionError(Exception):
def __init__(self, reason: str, transaction_id: str):
self.reason = reason
self.transaction_id = transaction_id
super().__init__(f"Invalid transaction {transaction_id}: {reason}")
# Использование
def transfer_money(from_account: int, to_account: int, amount: float):
balance = get_balance(from_account)
if balance < amount:
raise InsufficientFundsError(required=amount, available=balance)
if from_account == to_account:
raise InvalidTransactionError(
reason="Cannot transfer to same account",
transaction_id=str(uuid.uuid4())
)
# Выполняем транзакцию
process_transfer(from_account, to_account, amount)
9. Контекстный менеджер для исключений
from contextlib import contextmanager
class TimeoutError(Exception):
pass
@contextmanager
def timeout(seconds: int):
import signal
def timeout_handler(signum, frame):
raise TimeoutError(f"Operation timed out after {seconds} seconds")
signal.signal(signal.SIGALRM, timeout_handler)
signal.alarm(seconds)
try:
yield
finally:
signal.alarm(0)
# Использование
try:
with timeout(2):
time.sleep(3) # Это вызовет TimeoutError
except TimeoutError as e:
print(f"Timeout: {e}")
10. Лучшие практики
Структурируйте иерархию:
# Приложение
class MyAppError(Exception):
pass
# Слой API
class APIError(MyAppError):
pass
class ValidationError(APIError):
pass
class AuthenticationError(APIError):
pass
# Слой БД
class DatabaseError(MyAppError):
pass
class DuplicateKeyError(DatabaseError):
pass
# Обработка
try:
api_call()
except ValidationError as e:
return Response(status=400, body={"error": str(e)})
except AuthenticationError as e:
return Response(status=401, body={"error": str(e)})
except APIError as e:
return Response(status=500, body={"error": str(e)})
Документируйте:
class RateLimitError(Exception):
"""Выбрасывается когда превышена частота запросов.
Args:
retry_after: Количество секунд, которые нужно ждать перед повторной попыткой
"""
def __init__(self, retry_after: int):
self.retry_after = retry_after
super().__init__(f"Rate limit exceeded. Retry after {retry_after} seconds")
Итог
- Наследуйте от Exception для пользовательских исключений
- Создавайте иерархию для логичной обработки
- Добавляйте контекст (поля, методы) для отладки
- Используйте raise ... from ... для сохранения оригинального traceback
- Документируйте исключения в docstring функций