python-error-handling
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePython Error Handling
Python 错误处理
Build robust Python applications with proper input validation, meaningful exceptions, and graceful failure handling. Good error handling makes debugging easier and systems more reliable.
通过恰当的输入验证、有意义的异常和优雅的失败处理来构建健壮的Python应用。良好的错误处理能让调试更简单,系统更可靠。
When to Use This Skill
何时使用该技巧
- Validating user input and API parameters
- Designing exception hierarchies for applications
- Handling partial failures in batch operations
- Converting external data to domain types
- Building user-friendly error messages
- Implementing fail-fast validation patterns
- 验证用户输入和API参数
- 为应用设计异常层级结构
- 处理批量操作中的部分失败
- 将外部数据转换为领域类型
- 构建用户友好的错误消息
- 实现快速失败的验证模式
Core Concepts
核心概念
1. Fail Fast
1. 快速失败
Validate inputs early, before expensive operations. Report all validation errors at once when possible.
在执行昂贵操作前尽早验证输入。尽可能一次性报告所有验证错误。
2. Meaningful Exceptions
2. 有意义的异常
Use appropriate exception types with context. Messages should explain what failed, why, and how to fix it.
使用带有上下文的合适异常类型。错误消息应说明失败内容、原因以及修复方法。
3. Partial Failures
3. 部分失败处理
In batch operations, don't let one failure abort everything. Track successes and failures separately.
在批量操作中,不要因单个失败而终止整个流程。分别跟踪成功和失败的任务。
4. Preserve Context
4. 保留上下文
Chain exceptions to maintain the full error trail for debugging.
链式抛出异常以保留完整的错误追踪信息,便于调试。
Quick Start
快速开始
python
def fetch_page(url: str, page_size: int) -> Page:
if not url:
raise ValueError("'url' is required")
if not 1 <= page_size <= 100:
raise ValueError(f"'page_size' must be 1-100, got {page_size}")
# Now safe to proceed...python
def fetch_page(url: str, page_size: int) -> Page:
if not url:
raise ValueError("'url' is required")
if not 1 <= page_size <= 100:
raise ValueError(f"'page_size' must be 1-100, got {page_size}")
# Now safe to proceed...Fundamental Patterns
基础模式
Pattern 1: Early Input Validation
模式1:提前输入验证
Validate all inputs at API boundaries before any processing begins.
python
def process_order(
order_id: str,
quantity: int,
discount_percent: float,
) -> OrderResult:
"""Process an order with validation."""
# Validate required fields
if not order_id:
raise ValueError("'order_id' is required")
# Validate ranges
if quantity <= 0:
raise ValueError(f"'quantity' must be positive, got {quantity}")
if not 0 <= discount_percent <= 100:
raise ValueError(
f"'discount_percent' must be 0-100, got {discount_percent}"
)
# Validation passed, proceed with processing
return _process_validated_order(order_id, quantity, discount_percent)在开始任何处理前,先在API边界验证所有输入。
python
def process_order(
order_id: str,
quantity: int,
discount_percent: float,
) -> OrderResult:
"""Process an order with validation."""
# Validate required fields
if not order_id:
raise ValueError("'order_id' is required")
# Validate ranges
if quantity <= 0:
raise ValueError(f"'quantity' must be positive, got {quantity}")
if not 0 <= discount_percent <= 100:
raise ValueError(
f"'discount_percent' must be 0-100, got {discount_percent}"
)
# Validation passed, proceed with processing
return _process_validated_order(order_id, quantity, discount_percent)Pattern 2: Convert to Domain Types Early
模式2:提前转换为领域类型
Parse strings and external data into typed domain objects at system boundaries.
python
from enum import Enum
class OutputFormat(Enum):
JSON = "json"
CSV = "csv"
PARQUET = "parquet"
def parse_output_format(value: str) -> OutputFormat:
"""Parse string to OutputFormat enum.
Args:
value: Format string from user input.
Returns:
Validated OutputFormat enum member.
Raises:
ValueError: If format is not recognized.
"""
try:
return OutputFormat(value.lower())
except ValueError:
valid_formats = [f.value for f in OutputFormat]
raise ValueError(
f"Invalid format '{value}'. "
f"Valid options: {', '.join(valid_formats)}"
)在系统边界将字符串和外部数据解析为类型化的领域对象。
python
from enum import Enum
class OutputFormat(Enum):
JSON = "json"
CSV = "csv"
PARQUET = "parquet"
def parse_output_format(value: str) -> OutputFormat:
"""Parse string to OutputFormat enum.
Args:
value: Format string from user input.
Returns:
Validated OutputFormat enum member.
Raises:
ValueError: If format is not recognized.
"""
try:
return OutputFormat(value.lower())
except ValueError:
valid_formats = [f.value for f in OutputFormat]
raise ValueError(
f"Invalid format '{value}'. "
f"Valid options: {', '.join(valid_formats)}"
)Usage at API boundary
Usage at API boundary
def export_data(data: list[dict], format_str: str) -> bytes:
output_format = parse_output_format(format_str) # Fail fast
# Rest of function uses typed OutputFormat
...
undefineddef export_data(data: list[dict], format_str: str) -> bytes:
output_format = parse_output_format(format_str) # Fail fast
# Rest of function uses typed OutputFormat
...
undefinedPattern 3: Pydantic for Complex Validation
模式3:使用Pydantic进行复杂验证
Use Pydantic models for structured input validation with automatic error messages.
python
from pydantic import BaseModel, Field, field_validator
class CreateUserInput(BaseModel):
"""Input model for user creation."""
email: str = Field(..., min_length=5, max_length=255)
name: str = Field(..., min_length=1, max_length=100)
age: int = Field(ge=0, le=150)
@field_validator("email")
@classmethod
def validate_email_format(cls, v: str) -> str:
if "@" not in v or "." not in v.split("@")[-1]:
raise ValueError("Invalid email format")
return v.lower()
@field_validator("name")
@classmethod
def normalize_name(cls, v: str) -> str:
return v.strip().title()使用Pydantic模型进行结构化输入验证,自动生成错误消息。
python
from pydantic import BaseModel, Field, field_validator
class CreateUserInput(BaseModel):
"""Input model for user creation."""
email: str = Field(..., min_length=5, max_length=255)
name: str = Field(..., min_length=1, max_length=100)
age: int = Field(ge=0, le=150)
@field_validator("email")
@classmethod
def validate_email_format(cls, v: str) -> str:
if "@" not in v or "." not in v.split("@")[-1]:
raise ValueError("Invalid email format")
return v.lower()
@field_validator("name")
@classmethod
def normalize_name(cls, v: str) -> str:
return v.strip().title()Usage
Usage
try:
user_input = CreateUserInput(
email="user@example.com",
name="john doe",
age=25,
)
except ValidationError as e:
# Pydantic provides detailed error information
print(e.errors())
undefinedtry:
user_input = CreateUserInput(
email="user@example.com",
name="john doe",
age=25,
)
except ValidationError as e:
# Pydantic provides detailed error information
print(e.errors())
undefinedPattern 4: Map Errors to Standard Exceptions
模式4:将错误映射为标准异常
Use Python's built-in exception types appropriately, adding context as needed.
| Failure Type | Exception | Example |
|---|---|---|
| Invalid input | | Bad parameter values |
| Wrong type | | Expected string, got int |
| Missing item | | Dict key not found |
| Operational failure | | Service unavailable |
| Timeout | | Operation took too long |
| File not found | | Path doesn't exist |
| Permission denied | | Access forbidden |
python
undefined合理使用Python内置的异常类型,并根据需要添加上下文。
| 失败类型 | 异常 | 示例 |
|---|---|---|
| 无效输入 | | 参数值错误 |
| 类型错误 | | 期望字符串,实际传入整数 |
| 项缺失 | | 字典键不存在 |
| 操作失败 | | 服务不可用 |
| 超时 | | 操作耗时过长 |
| 文件未找到 | | 路径不存在 |
| 权限拒绝 | | 访问被禁止 |
python
undefinedGood: Specific exception with context
Good: Specific exception with context
raise ValueError(f"'page_size' must be 1-100, got {page_size}")
raise ValueError(f"'page_size' must be 1-100, got {page_size}")
Avoid: Generic exception, no context
Avoid: Generic exception, no context
raise Exception("Invalid parameter")
undefinedraise Exception("Invalid parameter")
undefinedAdvanced Patterns
高级模式
Pattern 5: Custom Exceptions with Context
模式5:带上下文的自定义异常
Create domain-specific exceptions that carry structured information.
python
class ApiError(Exception):
"""Base exception for API errors."""
def __init__(
self,
message: str,
status_code: int,
response_body: str | None = None,
) -> None:
self.status_code = status_code
self.response_body = response_body
super().__init__(message)
class RateLimitError(ApiError):
"""Raised when rate limit is exceeded."""
def __init__(self, retry_after: int) -> None:
self.retry_after = retry_after
super().__init__(
f"Rate limit exceeded. Retry after {retry_after}s",
status_code=429,
)创建携带结构化信息的领域特定异常。
python
class ApiError(Exception):
"""Base exception for API errors."""
def __init__(
self,
message: str,
status_code: int,
response_body: str | None = None,
) -> None:
self.status_code = status_code
self.response_body = response_body
super().__init__(message)
class RateLimitError(ApiError):
"""Raised when rate limit is exceeded."""
def __init__(self, retry_after: int) -> None:
self.retry_after = retry_after
super().__init__(
f"Rate limit exceeded. Retry after {retry_after}s",
status_code=429,
)Usage
Usage
def handle_response(response: Response) -> dict:
match response.status_code:
case 200:
return response.json()
case 401:
raise ApiError("Invalid credentials", 401)
case 404:
raise ApiError(f"Resource not found: {response.url}", 404)
case 429:
retry_after = int(response.headers.get("Retry-After", 60))
raise RateLimitError(retry_after)
case code if 400 <= code < 500:
raise ApiError(f"Client error: {response.text}", code)
case code if code >= 500:
raise ApiError(f"Server error: {response.text}", code)
undefineddef handle_response(response: Response) -> dict:
match response.status_code:
case 200:
return response.json()
case 401:
raise ApiError("Invalid credentials", 401)
case 404:
raise ApiError(f"Resource not found: {response.url}", 404)
case 429:
retry_after = int(response.headers.get("Retry-After", 60))
raise RateLimitError(retry_after)
case code if 400 <= code < 500:
raise ApiError(f"Client error: {response.text}", code)
case code if code >= 500:
raise ApiError(f"Server error: {response.text}", code)
undefinedPattern 6: Exception Chaining
模式6:异常链式传递
Preserve the original exception when re-raising to maintain the debug trail.
python
import httpx
class ServiceError(Exception):
"""High-level service operation failed."""
pass
def upload_file(path: str) -> str:
"""Upload file and return URL."""
try:
with open(path, "rb") as f:
response = httpx.post("https://upload.example.com", files={"file": f})
response.raise_for_status()
return response.json()["url"]
except FileNotFoundError as e:
raise ServiceError(f"Upload failed: file not found at '{path}'") from e
except httpx.HTTPStatusError as e:
raise ServiceError(
f"Upload failed: server returned {e.response.status_code}"
) from e
except httpx.RequestError as e:
raise ServiceError(f"Upload failed: network error") from e重新抛出异常时保留原始异常,以维护调试追踪信息。
python
import httpx
class ServiceError(Exception):
"""High-level service operation failed."""
pass
def upload_file(path: str) -> str:
"""Upload file and return URL."""
try:
with open(path, "rb") as f:
response = httpx.post("https://upload.example.com", files={"file": f})
response.raise_for_status()
return response.json()["url"]
except FileNotFoundError as e:
raise ServiceError(f"Upload failed: file not found at '{path}'") from e
except httpx.HTTPStatusError as e:
raise ServiceError(
f"Upload failed: server returned {e.response.status_code}"
) from e
except httpx.RequestError as e:
raise ServiceError(f"Upload failed: network error") from ePattern 7: Batch Processing with Partial Failures
模式7:处理部分失败的批处理
Never let one bad item abort an entire batch. Track results per item.
python
from dataclasses import dataclass
@dataclass
class BatchResult[T]:
"""Results from batch processing."""
succeeded: dict[int, T] # index -> result
failed: dict[int, Exception] # index -> error
@property
def success_count(self) -> int:
return len(self.succeeded)
@property
def failure_count(self) -> int:
return len(self.failed)
@property
def all_succeeded(self) -> bool:
return len(self.failed) == 0
def process_batch(items: list[Item]) -> BatchResult[ProcessedItem]:
"""Process items, capturing individual failures.
Args:
items: Items to process.
Returns:
BatchResult with succeeded and failed items by index.
"""
succeeded: dict[int, ProcessedItem] = {}
failed: dict[int, Exception] = {}
for idx, item in enumerate(items):
try:
result = process_single_item(item)
succeeded[idx] = result
except Exception as e:
failed[idx] = e
return BatchResult(succeeded=succeeded, failed=failed)不要因单个错误项终止整个批次。跟踪每个项的处理结果。
python
from dataclasses import dataclass
@dataclass
class BatchResult[T]:
"""Results from batch processing."""
succeeded: dict[int, T] # index -> result
failed: dict[int, Exception] # index -> error
@property
def success_count(self) -> int:
return len(self.succeeded)
@property
def failure_count(self) -> int:
return len(self.failed)
@property
def all_succeeded(self) -> bool:
return len(self.failed) == 0
def process_batch(items: list[Item]) -> BatchResult[ProcessedItem]:
"""Process items, capturing individual failures.
Args:
items: Items to process.
Returns:
BatchResult with succeeded and failed items by index.
"""
succeeded: dict[int, ProcessedItem] = {}
failed: dict[int, Exception] = {}
for idx, item in enumerate(items):
try:
result = process_single_item(item)
succeeded[idx] = result
except Exception as e:
failed[idx] = e
return BatchResult(succeeded=succeeded, failed=failed)Caller handles partial results
Caller handles partial results
result = process_batch(items)
if not result.all_succeeded:
logger.warning(
f"Batch completed with {result.failure_count} failures",
failed_indices=list(result.failed.keys()),
)
undefinedresult = process_batch(items)
if not result.all_succeeded:
logger.warning(
f"Batch completed with {result.failure_count} failures",
failed_indices=list(result.failed.keys()),
)
undefinedPattern 8: Progress Reporting for Long Operations
模式8:长时操作的进度报告
Provide visibility into batch progress without coupling business logic to UI.
python
from collections.abc import Callable
ProgressCallback = Callable[[int, int, str], None] # current, total, status
def process_large_batch(
items: list[Item],
on_progress: ProgressCallback | None = None,
) -> BatchResult:
"""Process batch with optional progress reporting.
Args:
items: Items to process.
on_progress: Optional callback receiving (current, total, status).
"""
total = len(items)
succeeded = {}
failed = {}
for idx, item in enumerate(items):
if on_progress:
on_progress(idx, total, f"Processing {item.id}")
try:
succeeded[idx] = process_single_item(item)
except Exception as e:
failed[idx] = e
if on_progress:
on_progress(total, total, "Complete")
return BatchResult(succeeded=succeeded, failed=failed)在不将业务逻辑与UI耦合的前提下,提供批处理进度的可见性。
python
from collections.abc import Callable
ProgressCallback = Callable[[int, int, str], None] # current, total, status
def process_large_batch(
items: list[Item],
on_progress: ProgressCallback | None = None,
) -> BatchResult:
"""Process batch with optional progress reporting.
Args:
items: Items to process.
on_progress: Optional callback receiving (current, total, status).
"""
total = len(items)
succeeded = {}
failed = {}
for idx, item in enumerate(items):
if on_progress:
on_progress(idx, total, f"Processing {item.id}")
try:
succeeded[idx] = process_single_item(item)
except Exception as e:
failed[idx] = e
if on_progress:
on_progress(total, total, "Complete")
return BatchResult(succeeded=succeeded, failed=failed)Best Practices Summary
最佳实践总结
- Validate early - Check inputs before expensive operations
- Use specific exceptions - ,
ValueError, not genericTypeErrorException - Include context - Messages should explain what, why, and how to fix
- Convert types at boundaries - Parse strings to enums/domain types early
- Chain exceptions - Use to preserve debug info
raise ... from e - Handle partial failures - Don't abort batches on single item errors
- Use Pydantic - For complex input validation with structured errors
- Document failure modes - Docstrings should list possible exceptions
- Log with context - Include IDs, counts, and other debugging info
- Test error paths - Verify exceptions are raised correctly
- 尽早验证 - 在执行昂贵操作前检查输入
- 使用特定异常 - 使用、
ValueError等,而非通用的TypeErrorException - 包含上下文 - 错误消息应说明失败内容、原因及修复方法
- 在边界转换类型 - 尽早将字符串解析为枚举/领域类型
- 链式传递异常 - 使用保留调试信息
raise ... from e - 处理部分失败 - 不要因单个项错误终止批次
- 使用Pydantic - 用于复杂输入验证,生成结构化错误
- 记录失败模式 - 文档字符串应列出可能抛出的异常
- 带上下文日志 - 包含ID、计数等调试信息
- 测试错误路径 - 验证异常是否被正确抛出