pin_drop当前位置:知识文库 ❯ 图文
Python自定义异常详解 - 继承Exception创建业务异常
一、为什么要自定义异常
Python虽然提供了丰富的内置异常类,但在实际开发中,我们常常需要表达特定业务场景下的错误情况。自定义异常可以让我们:
-
语义化错误:用业务相关的名称命名异常,使代码更易读
-
携带额外信息:在异常对象中附加错误码、上下文等数据
-
精确捕获:调用者可以专门捕获你定义的业务异常
-
统一规范:在大型项目中建立统一的异常体系
二、继承Exception创建自定义异常
自定义异常类最简单的方式是直接继承Exception类。按照Python惯例,异常类名通常以Error或Exception结尾。
代码示例
# 最简单的自定义异常
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}")五、代码示例
示例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:异常类名应以
Error或Exception结尾,这是Python社区的命名约定,有助于其他开发者快速识别异常类。
注意3:如果需要在异常中存储额外数据,确保在
__init__中调用super().__init__(message),这样异常对象才能正确传递消息字符串。
七、小结
-
继承Exception:自定义异常类应继承Exception,类名以Error或Exception结尾
-
自定义属性:通过重写__init__方法添加错误码、详情等属性,使异常携带更丰富的信息
-
层级设计:大型项目应设计异常基类和子类层次,支持从粗到细的异常捕获粒度
-
语义清晰:异常名称应清楚表达错误类型,让调用者一目了然
小贴士
在自定义异常中使用__str__方法可以控制异常被打印时的显示格式。如果还希望支持pickle序列化(用于多进程通信),可以同时定义__reduce__方法。
八、练习题
练习1
创建一个ConfigError异常类,用于配置文件读取场景。要求包含config_file和key两个属性,并在__str__方法中格式化为:"[ConfigError] 文件xxx中缺少键:yyy"。
练习2
设计一个用户管理系统,创建UserError基类及其子类UsernameExistsError、PasswordTooWeakError、EmailInvalidError,编写注册函数并在实际场景中抛出和捕获这些异常。
常见问题
自定义异常应该继承Exception还是BaseException?
应该始终继承Exception。BaseException包含了SystemExit、KeyboardInterrupt等系统级异常,继承它会导致你的自定义异常被不恰当地处理,也可能干扰程序的正常退出流程。
自定义异常中是否必须调用super().__init__()?
强烈建议调用。super().__init__(message)确保异常对象正确初始化消息属性,这样在打印异常、日志记录、traceback输出时才能显示正确的错误信息。如果不调用,异常对象的args属性可能为空。
一个项目应该定义多少个自定义异常类?
取决于项目复杂度。小型项目可能只需要1-2个通用异常类,而大型项目可能需要20+个异常类。关键原则是:每种需要不同处理方式的错误都应该有独立的异常类。如果多种错误都用相同方式处理,可以用一个通用异常类加错误码区分。
能否在自定义异常中存储复杂对象?
可以,但需谨慎。存储简单数据类型(字符串、数字、字典)是安全的。存储复杂对象(如数据库连接、文件句柄)可能导致资源泄漏或序列化问题。建议只存储必要的上下文信息,如ID、名称、错误码等。
本文涉及AI创作
内容由AI创作,请仔细甄别