Python 自訂例外 (Custom Exceptions)
你可以建立自己的例外類別,讓錯誤處理更有語意且更容易管理。
建立自訂例外
繼承 Exception 類別:
class MyError(Exception):
pass
# 使用
raise MyError("Something went wrong")
加入自訂屬性
class ValidationError(Exception):
def __init__(self, field, message):
self.field = field
self.message = message
super().__init__(f"{field}: {message}")
try:
raise ValidationError("email", "Invalid email format")
except ValidationError as e:
print(f"Field: {e.field}")
print(f"Message: {e.message}")
輸出:
Field: email
Message: Invalid email format
建立例外階層
# 基礎例外
class AppError(Exception):
"""應用程式的基礎例外"""
pass
# 資料庫相關例外
class DatabaseError(AppError):
"""資料庫相關錯誤"""
pass
class ConnectionError(DatabaseError):
"""資料庫連線錯誤"""
pass
class QueryError(DatabaseError):
"""查詢錯誤"""
pass
# 驗證相關例外
class ValidationError(AppError):
"""驗證錯誤"""
pass
class RequiredFieldError(ValidationError):
"""必填欄位錯誤"""
pass
class InvalidFormatError(ValidationError):
"""格式錯誤"""
pass
使用例外階層可以靈活地捕捉例外:
try:
# 可能拋出各種例外的程式碼
pass
except ConnectionError:
# 只處理連線錯誤
pass
except DatabaseError:
# 處理所有資料庫錯誤(包括 ConnectionError 和 QueryError)
pass
except AppError:
# 處理所有應用程式錯誤
pass
完整的自訂例外類別
class APIError(Exception):
"""API 相關錯誤的基礎類別"""
def __init__(self, message, status_code=None, details=None):
super().__init__(message)
self.message = message
self.status_code = status_code
self.details = details or {}
def to_dict(self):
return {
"error": self.__class__.__name__,
"message": self.message,
"status_code": self.status_code,
"details": self.details
}
class NotFoundError(APIError):
def __init__(self, resource, resource_id):
message = f"{resource} with id {resource_id} not found"
super().__init__(message, status_code=404)
self.resource = resource
self.resource_id = resource_id
class AuthenticationError(APIError):
def __init__(self, message="Authentication failed"):
super().__init__(message, status_code=401)
class PermissionError(APIError):
def __init__(self, message="Permission denied"):
super().__init__(message, status_code=403)
# 使用
try:
user_id = 123
user = find_user(user_id)
if not user:
raise NotFoundError("User", user_id)
except NotFoundError as e:
print(e.to_dict())
實際應用範例
表單驗證
class FormValidationError(Exception):
def __init__(self):
super().__init__("Form validation failed")
self.errors = {}
def add_error(self, field, message):
if field not in self.errors:
self.errors[field] = []
self.errors[field].append(message)
def has_errors(self):
return len(self.errors) > 0
def __str__(self):
error_messages = []
for field, messages in self.errors.items():
for msg in messages:
error_messages.append(f"{field}: {msg}")
return "\n".join(error_messages)
def validate_user_form(data):
errors = FormValidationError()
if not data.get("username"):
errors.add_error("username", "Username is required")
elif len(data["username"]) < 3:
errors.add_error("username", "Username must be at least 3 characters")
if not data.get("email"):
errors.add_error("email", "Email is required")
elif "@" not in data["email"]:
errors.add_error("email", "Invalid email format")
if not data.get("password"):
errors.add_error("password", "Password is required")
elif len(data["password"]) < 8:
errors.add_error("password", "Password must be at least 8 characters")
if errors.has_errors():
raise errors
return True
# 使用
try:
validate_user_form({
"username": "ab",
"email": "invalid-email",
"password": "123"
})
except FormValidationError as e:
print("Validation errors:")
print(e)
業務邏輯例外
class InsufficientFundsError(Exception):
def __init__(self, balance, amount):
self.balance = balance
self.amount = amount
super().__init__(
f"Insufficient funds: balance={balance}, required={amount}"
)
class AccountLockedError(Exception):
def __init__(self, account_id, reason):
self.account_id = account_id
self.reason = reason
super().__init__(f"Account {account_id} is locked: {reason}")
class BankAccount:
def __init__(self, account_id, balance=0):
self.account_id = account_id
self.balance = balance
self.locked = False
self.lock_reason = None
def withdraw(self, amount):
if self.locked:
raise AccountLockedError(self.account_id, self.lock_reason)
if amount > self.balance:
raise InsufficientFundsError(self.balance, amount)
self.balance -= amount
return self.balance
def lock(self, reason):
self.locked = True
self.lock_reason = reason
# 使用
account = BankAccount("A001", 1000)
try:
account.withdraw(1500)
except InsufficientFundsError as e:
print(f"Cannot withdraw: {e}")
print(f"Current balance: {e.balance}")
print(f"Requested amount: {e.amount}")
Context Manager 中的例外
class TransactionError(Exception):
pass
class Transaction:
def __init__(self, name):
self.name = name
def __enter__(self):
print(f"Starting transaction: {self.name}")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is not None:
print(f"Rolling back transaction: {self.name}")
print(f"Error: {exc_val}")
# 回傳 True 表示例外已處理
# 回傳 False 表示讓例外繼續傳播
return False
print(f"Committing transaction: {self.name}")
return True
def execute(self, query):
print(f"Executing: {query}")
if "error" in query:
raise TransactionError("Query failed")
# 使用
with Transaction("update_user") as t:
t.execute("UPDATE users SET name='Alice'")
t.execute("This will cause error") # 會觸發回滾
最佳實踐
- 繼承 Exception 而不是 BaseException
- 建立有意義的例外階層
- 包含有用的錯誤資訊
- 保持例外類別簡單
- 為相關的例外建立基礎類別
# 好的例外設計
class OrderError(Exception):
"""訂單相關錯誤的基礎類別"""
pass
class OrderNotFoundError(OrderError):
def __init__(self, order_id):
self.order_id = order_id
super().__init__(f"Order {order_id} not found")
class InvalidOrderStatusError(OrderError):
def __init__(self, order_id, current_status, required_status):
self.order_id = order_id
self.current_status = current_status
self.required_status = required_status
super().__init__(
f"Order {order_id} has status '{current_status}', "
f"but '{required_status}' is required"
)