pin_drop当前位置:知识文库 ❯ 图文

Python自定义异常详解 - 继承Exception创建业务异常

一、为什么要自定义异常

Python虽然提供了丰富的内置异常类,但在实际开发中,我们常常需要表达特定业务场景下的错误情况。自定义异常可以让我们:

  • 语义化错误:用业务相关的名称命名异常,使代码更易读

  • 携带额外信息:在异常对象中附加错误码、上下文等数据

  • 精确捕获:调用者可以专门捕获你定义的业务异常

  • 统一规范:在大型项目中建立统一的异常体系


二、继承Exception创建自定义异常

自定义异常类最简单的方式是直接继承Exception类。按照Python惯例,异常类名通常以ErrorException结尾。

代码示例

# 最简单的自定义异常
class MyCustomError(Exception):
    """自定义异常基类"""
    pass

# 使用自定义异常
def check_value(value):
    if value < 0:
        raise MyCustomError("值不能为负数")
    return value * 2

try:
    check_value(-5)
except MyCustomError as e:
    print(f"捕获到异常:{e}")

这种简单定义已经可以满足基本需求,但更完善的自定义异常通常会重写__init____str__方法。


三、添加自定义属性

自定义异常的强大之处在于可以携带额外的错误信息,比如错误码、错误详情、发生时间等。

代码示例

class APIError(Exception):
    """API调用异常,携带错误码和详情"""
    
    def __init__(self, error_code, message, details=None):
        self.error_code = error_code
        self.message = message
        self.details = details
        super().__init__(self.message)
    
    def __str__(self):
        return f"[{self.error_code}] {self.message}"
    
    def __repr__(self):
        return f"{self.__class__.__name__}(error_code={self.error_code!r}, message={self.message!r})"

# 使用
def call_api(endpoint):
    if endpoint == "/users":
        raise APIError(404, "用户接口未找到", {"path": "/users"})
    return {"data": "success"}

try:
    call_api("/users")
except APIError as e:
    print(f"错误码:{e.error_code}")
    print(f"错误信息:{e.message}")
    print(f"详细信息:{e.details}")
    print(f"异常字符串:{e}")

四、异常类的继承层级设计

在大型项目中,通常会设计一个异常类层级结构,从基类到具体异常逐级细化:

代码示例

# 自定义异常层级设计
class AppError(Exception):
    """应用程序异常基类"""
    def __init__(self, message, error_code=None):
        self.error_code = error_code
        super().__init__(message)

class ValidationError(AppError):
    """数据验证异常"""
    pass

class DatabaseError(AppError):
    """数据库操作异常"""
    def __init__(self, message, query=None):
        self.query = query
        super().__init__(message, error_code="DB_ERR")

class AuthenticationError(AppError):
    """认证异常"""
    pass

class PermissionDeniedError(AuthenticationError):
    """权限不足异常"""
    pass

# 使用示例
def process_payment(amount, user_role):
    if amount <= 0:
        raise ValidationError("金额必须大于0", error_code="VAL_001")
    if user_role != "admin":
        raise PermissionDeniedError("只有管理员可以处理付款", error_code="AUTH_003")

try:
    process_payment(-100, "user")
except ValidationError as e:
    print(f"验证失败:{e},错误码:{e.error_code}")
except AppError as e:
    print(f"应用异常:{e}")
设计方式 优点 适用场景
简单继承Exception 代码简洁,快速实现 小型脚本、简单项目
添加自定义属性 携带丰富错误信息 API开发、微服务
设计异常层级 精确控制捕获粒度 大型项目、框架开发

五、代码示例

示例1:电商订单异常体系

代码示例

class OrderError(Exception):
    """订单异常基类"""
    def __init__(self, message, order_id=None):
        self.order_id = order_id
        super().__init__(message)

class InventoryError(OrderError):
    """库存不足异常"""
    def __init__(self, message, product_id=None, available=None):
        self.product_id = product_id
        self.available = available
        super().__init__(message)

class PaymentError(OrderError):
    """支付异常"""
    def __init__(self, message, amount=None, method=None):
        self.amount = amount
        self.method = method
        super().__init__(message)

def place_order(product_id, quantity, user_balance):
    stock = get_stock(product_id)
    if quantity > stock:
        raise InventoryError(
            f"商品{product_id}库存不足",
            product_id=product_id,
            available=stock
        )
    
    price = get_price(product_id) * quantity
    if user_balance < price:
        raise PaymentError(
            f"余额不足,需要{price}元",
            amount=price,
            method="balance"
        )
    
    return {"status": "success", "order_id": "ORD001"}

def get_stock(pid): return 5
def get_price(pid): return 100

# 测试
try:
    place_order("P001", 10, 500)
except InventoryError as e:
    print(f"库存问题:{e},当前库存:{e.available}")
except PaymentError as e:
    print(f"支付问题:{e},需要金额:{e.amount}")
except OrderError as e:
    print(f"订单异常:{e}")

示例2:带重试机制的自定义异常

代码示例

class RetryableError(Exception):
    """可重试的错误"""
    def __init__(self, message, retry_after=None):
        self.retry_after = retry_after
        super().__init__(message)

def retry(func, max_retries=3):
    """带重试的函数执行器"""
    for attempt in range(max_retries):
        try:
            return func()
        except RetryableError as e:
            print(f"第{attempt + 1}次失败:{e}")
            if attempt == max_retries - 1:
                raise
    return None

# 模拟网络请求
request_count = 0
def mock_api_call():
    global request_count
    request_count += 1
    if request_count < 3:
        raise RetryableError("服务暂时不可用", retry_after=1)
    return "success"

result = retry(mock_api_call, max_retries=3)
print(f"最终结果:{result}")

六、注意事项

注意1:自定义异常应始终继承Exception而不是BaseException。继承BaseException会导致异常被系统级处理机制错误拦截。

注意2:异常类名应以ErrorException结尾,这是Python社区的命名约定,有助于其他开发者快速识别异常类。

注意3:如果需要在异常中存储额外数据,确保在__init__中调用super().__init__(message),这样异常对象才能正确传递消息字符串。


七、小结

  • 继承Exception:自定义异常类应继承Exception,类名以Error或Exception结尾

  • 自定义属性:通过重写__init__方法添加错误码、详情等属性,使异常携带更丰富的信息

  • 层级设计:大型项目应设计异常基类和子类层次,支持从粗到细的异常捕获粒度

  • 语义清晰:异常名称应清楚表达错误类型,让调用者一目了然

小贴士

在自定义异常中使用__str__方法可以控制异常被打印时的显示格式。如果还希望支持pickle序列化(用于多进程通信),可以同时定义__reduce__方法。


八、练习题

练习1

创建一个ConfigError异常类,用于配置文件读取场景。要求包含config_filekey两个属性,并在__str__方法中格式化为:"[ConfigError] 文件xxx中缺少键:yyy"。

练习2

设计一个用户管理系统,创建UserError基类及其子类UsernameExistsErrorPasswordTooWeakErrorEmailInvalidError,编写注册函数并在实际场景中抛出和捕获这些异常。

常见问题

自定义异常应该继承Exception还是BaseException?

应该始终继承Exception。BaseException包含了SystemExit、KeyboardInterrupt等系统级异常,继承它会导致你的自定义异常被不恰当地处理,也可能干扰程序的正常退出流程。

自定义异常中是否必须调用super().__init__()?

强烈建议调用。super().__init__(message)确保异常对象正确初始化消息属性,这样在打印异常、日志记录、traceback输出时才能显示正确的错误信息。如果不调用,异常对象的args属性可能为空。

一个项目应该定义多少个自定义异常类?

取决于项目复杂度。小型项目可能只需要1-2个通用异常类,而大型项目可能需要20+个异常类。关键原则是:每种需要不同处理方式的错误都应该有独立的异常类。如果多种错误都用相同方式处理,可以用一个通用异常类加错误码区分。

能否在自定义异常中存储复杂对象?

可以,但需谨慎。存储简单数据类型(字符串、数字、字典)是安全的。存储复杂对象(如数据库连接、文件句柄)可能导致资源泄漏或序列化问题。建议只存储必要的上下文信息,如ID、名称、错误码等。

标签: 自定义异常 Exception 异常类 错误处理 Python教程

本文涉及AI创作

内容由AI创作,请仔细甄别

list快速访问

上一篇: Python异常层级结构详解 - BaseException与Exception区别 下一篇: Python raise语句详解 - 主动抛出异常与异常链

poll相关推荐