自定义异常
当内置异常无法准确描述你的程序错误时,可以定义自己的异常类。自定义异常让错误信息更清晰,也让调用方可以精确捕获特定错误。
基本定义
自定义异常通常继承自 Exception 或其子类:
class ValidationError(Exception):
"""数据验证失败时抛出。"""
pass
class ConfigError(Exception):
"""配置文件错误。"""
pass
使用:
def set_port(port):
if not (0 <= port <= 65535):
raise ValidationError("Port must be 0-65535, got %d" % port)
try:
set_port(70000)
except ValidationError as e:
print "Invalid:", e # Invalid: Port must be 0-65535, got 70000
添加自定义属性
可以在异常类中添加额外信息:
class APIError(Exception):
"""API 调用错误。"""
def __init__(self, message, status_code, response_body):
super(APIError, self).__init__(message)
self.status_code = status_code
self.response_body = response_body
try:
raise APIError("Server error", 500, "Internal Server Error")
except APIError as e:
print e.status_code # 500
print e.response_body # Internal Server Error
print str(e) # Server error
注意:Python 2 中 Exception 的构造比较特殊,自定义 __init__ 时建议调用父类的 __init__。
异常层次结构
为相关异常建立层次结构,让调用方可以选择捕获粒度:
class DatabaseError(Exception):
"""数据库操作错误基类。"""
pass
class ConnectionError(DatabaseError):
"""连接失败。"""
pass
class QueryError(DatabaseError):
"""查询执行失败。"""
pass
class IntegrityError(QueryError):
"""违反完整性约束。"""
pass
调用方可以精确捕获或宽泛捕获:
try:
db.execute(query)
except IntegrityError:
# 处理唯一约束冲突
handle_duplicate()
except QueryError:
# 处理其他查询错误
log_query_error()
except DatabaseError:
# 处理所有数据库错误
log_db_error()
从内置异常继承
如果错误与内置异常语义接近,可以继承内置异常:
class FileFormatError(ValueError):
"""文件格式不正确。"""
pass
class NetworkTimeout(IOError):
"""网络请求超时。"""
pass
这样调用方可以用 except ValueError 捕获 FileFormatError,也可以用 except FileFormatError 精确捕获。
实际应用
Web 框架的 HTTP 异常:
class HTTPError(Exception):
def __init__(self, status_code, message=""):
self.status_code = status_code
self.message = message
super(HTTPError, self).__init__("HTTP %d: %s" % (status_code, message))
class NotFound(HTTPError):
def __init__(self, resource):
super(NotFound, self).__init__(404, "Resource not found: %s" % resource)
class Forbidden(HTTPError):
def __init__(self, permission):
super(Forbidden, self).__init__(403, "Permission denied: %s" % permission)
# 使用
raise NotFound("/users/123")
业务逻辑异常:
class BusinessError(Exception):
"""业务规则违反。"""
code = None
class InsufficientFunds(BusinessError):
code = "E001"
def __init__(self, balance, required):
super(InsufficientFunds, self).__init__(
"Balance %d < required %d" % (balance, required)
)
self.balance = balance
self.required = required
class ProductOutOfStock(BusinessError):
code = "E002"
def __init__(self, product_id, available):
super(ProductOutOfStock, self).__init__(
"Product %s: only %d available" % (product_id, available)
)
异常命名规范
- 以
Error结尾:ValueError、ConnectionError - 使用 PascalCase:
DatabaseError、FileNotFound - 描述错误类型,而非错误场景:
InvalidInputError而非UserTypedWrong - 继承关系反映错误分类,而非模块结构
与 Python 2 旧语法的兼容
Python 2 支持 raise Exception, value 语法:
raise ValueError, "Invalid input" # 旧语法
raise ValueError("Invalid input") # 新语法(推荐)
自定义异常时,始终使用新语法 raise ClassName(args)。