目录

Python 自定义异常

学习目标

  • 掌握自定义异常类的创建方法
  • 理解异常继承的最佳实践
  • 学会在项目中构建异常层次结构
  • 理解异常的传递和包装

1. 创建自定义异常

自定义异常需要继承自 Exception 或其子类。

1.1 最简单的自定义异常

class ValidationError(Exception):
    """数据验证错误"""
    pass

# 使用
raise ValidationError("年龄必须在 0-150 之间")

1.2 带参数的自定义异常

class BusinessError(Exception):
    """业务逻辑错误"""
    
    def __init__(self, message, code=None):
        super().__init__(message)
        self.code = code
        self.message = message
    
    def __str__(self):
        if self.code:
            return f"[{self.code}] {self.message}"
        return self.message

# 使用
try:
    raise BusinessError("用户不存在", code="USER_NOT_FOUND")
except BusinessError as e:
    print(e)          # [USER_NOT_FOUND] 用户不存在
    print(e.code)     # USER_NOT_FOUND
    print(e.message)  # 用户不存在

2. 异常层次结构

在项目中建立清晰的异常层次结构,便于调用者精确捕获。

# 基类 - 所有应用异常的根
class AppException(Exception):
    """应用异常基类"""
    def __init__(self, message, code="UNKNOWN"):
        super().__init__(message)
        self.code = code
        self.message = message

# 业务异常
class BusinessException(AppException):
    """业务逻辑异常"""
    def __init__(self, message, code="BUSINESS_ERROR"):
        super().__init__(message, code)

# 数据异常
class DataException(AppException):
    """数据相关异常"""
    def __init__(self, message, code="DATA_ERROR"):
        super().__init__(message, code)

# 具体业务异常
class UserNotFoundException(BusinessException):
    """用户不存在"""
    def __init__(self, user_id):
        super().__init__(f"用户 {user_id} 不存在", "USER_NOT_FOUND")
        self.user_id = user_id

class InvalidPasswordException(BusinessException):
    """密码错误"""
    def __init__(self):
        super().__init__("密码不正确", "INVALID_PASSWORD")

class DatabaseConnectionException(DataException):
    """数据库连接失败"""
    def __init__(self, host):
        super().__init__(f"无法连接到数据库: {host}", "DB_CONNECTION_ERROR")
        self.host = host

2.1 使用层次异常

def get_user(user_id):
    """获取用户"""
    if user_id <= 0:
        raise UserNotFoundException(user_id)
    return {"id": user_id, "name": "用户" + str(user_id)}

def login(username, password):
    """登录"""
    if password != "correct":
        raise InvalidPasswordException()
    return {"token": "abc123"}

# 统一处理
def handle_request():
    try:
        user = get_user(999)
        result = login(user["name"], "wrong")
    except UserNotFoundException as e:
        print(f"用户错误 [{e.code}]: {e.message}")
    except InvalidPasswordException as e:
        print(f"认证错误 [{e.code}]: {e.message}")
    except BusinessException as e:
        print(f"业务错误 [{e.code}]: {e.message}")
    except AppException as e:
        print(f"应用错误 [{e.code}]: {e.message}")
    except Exception as e:
        print(f"系统错误: {e}")

handle_request()

3. 异常包装

3.1 将底层异常转换为业务异常

import json

class ConfigError(AppException):
    """配置错误"""
    pass

def load_config(filename):
    """加载配置文件"""
    try:
        with open(filename, 'r') as f:
            return json.load(f)
    except FileNotFoundError as e:
        raise ConfigError(f"配置文件不存在: {filename}") from e
    except json.JSONDecodeError as e:
        raise ConfigError(f"配置文件格式错误: {e}") from e
    except PermissionError as e:
        raise ConfigError(f"无权读取配置文件: {filename}") from e

# 使用
try:
    config = load_config('app.json')
except ConfigError as e:
    print(f"配置加载失败: {e}")
    if e.__cause__:
        print(f"原始错误: {type(e.__cause__).__name__}: {e.__cause__}")

3.2 API 异常处理模式

class APIError(Exception):
    """API 错误"""
    def __init__(self, message, status_code=500, details=None):
        super().__init__(message)
        self.status_code = status_code
        self.details = details or {}
    
    def to_dict(self):
        return {
            "error": self.message,
            "code": self.status_code,
            "details": self.details
        }

class NotFoundError(APIError):
    def __init__(self, resource, resource_id):
        super().__init__(
            f"{resource} {resource_id} 不存在",
            status_code=404
        )

class ValidationError(APIError):
    def __init__(self, field, message):
        super().__init__(
            "数据验证失败",
            status_code=400,
            details={"field": field, "message": message}
        )

# 模拟 API 处理
def api_handler():
    try:
        # 模拟各种错误
        raise NotFoundError("用户", 123)
    except APIError as e:
        return e.to_dict()

print(api_handler())
# {'error': '用户 123 不存在', 'code': 404, 'details': {}}

4. 实际项目中的异常处理

4.1 完整的异常模块

# exceptions.py

class AppError(Exception):
    """应用基础异常"""
    status_code = 500
    error_code = "INTERNAL_ERROR"
    
    def __init__(self, message=None):
        self.message = message or "服务器内部错误"
        super().__init__(self.message)
    
    def to_response(self):
        return {
            "success": False,
            "error_code": self.error_code,
            "message": self.message
        }

class NotFoundError(AppError):
    """资源不存在"""
    status_code = 404
    error_code = "NOT_FOUND"
    
    def __init__(self, resource="资源"):
        super().__init__(f"{resource}不存在")

class BadRequestError(AppError):
    """请求参数错误"""
    status_code = 400
    error_code = "BAD_REQUEST"
    
    def __init__(self, message="请求参数错误"):
        super().__init__(message)

class UnauthorizedError(AppError):
    """未授权"""
    status_code = 401
    error_code = "UNAUTHORIZED"
    
    def __init__(self, message="请先登录"):
        super().__init__(message)

class ForbiddenError(AppError):
    """禁止访问"""
    status_code = 403
    error_code = "FORBIDDEN"
    
    def __init__(self, message="无权访问"):
        super().__init__(message)

4.2 使用示例

# service.py
from exceptions import NotFoundError, BadRequestError

class UserService:
    users = {1: {"name": "Alice"}, 2: {"name": "Bob"}}
    
    @classmethod
    def get_user(cls, user_id):
        if not isinstance(user_id, int) or user_id <= 0:
            raise BadRequestError("用户ID必须是正整数")
        
        user = cls.users.get(user_id)
        if not user:
            raise NotFoundError(f"用户(ID={user_id})")
        
        return user
    
    @classmethod
    def create_user(cls, name):
        if not name or len(name) < 2:
            raise BadRequestError("用户名至少需要2个字符")
        
        new_id = max(cls.users.keys()) + 1
        cls.users[new_id] = {"name": name}
        return cls.users[new_id]

# 使用
try:
    user = UserService.get_user(999)
    print(user)
except NotFoundError as e:
    print(f"404: {e.to_response()}")
except BadRequestError as e:
    print(f"400: {e.to_response()}")

5. 异常与上下文信息

5.1 添加上下文

class ContextualError(Exception):
    """带上下文的异常"""
    
    def __init__(self, message, **context):
        super().__init__(message)
        self.context = context
    
    def __str__(self):
        base = super().__str__()
        if self.context:
            context_str = ", ".join(f"{k}={v}" for k, v in self.context.items())
            return f"{base} (上下文: {context_str})"
        return base

# 使用
def process_order(order_id, user_id):
    raise ContextualError(
        "订单处理失败",
        order_id=order_id,
        user_id=user_id,
        timestamp="2024-01-01"
    )

try:
    process_order(1001, 42)
except ContextualError as e:
    print(e)
    print(f"上下文: {e.context}")

本节小结

  • 自定义异常:继承 Exception 或其子类
  • 层次结构:建立从通用到具体的异常树
  • 异常包装:将底层异常转换为业务异常,raise ... from 保留链路
  • 实际应用:为 API、业务逻辑设计统一的异常体系
  • 上下文信息:在异常中附加额外信息便于排查问题

练习

  1. 为你的项目设计一套三层异常体系(应用级、模块级、具体错误)
  2. 创建一个 RetryableError 异常,标记可以重试的操作错误
  3. 实现一个装饰器,自动将函数中的异常转换为指定的自定义异常
  4. 编写一个异常转换函数,将 Python 内置异常映射为你的业务异常