python-error-handling

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Python 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 ...
undefined
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 ...
undefined

Pattern 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())
undefined
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())
undefined

Pattern 4: Map Errors to Standard Exceptions

模式4:将错误映射为标准异常

Use Python's built-in exception types appropriately, adding context as needed.
Failure TypeExceptionExample
Invalid input
ValueError
Bad parameter values
Wrong type
TypeError
Expected string, got int
Missing item
KeyError
Dict key not found
Operational failure
RuntimeError
Service unavailable
Timeout
TimeoutError
Operation took too long
File not found
FileNotFoundError
Path doesn't exist
Permission denied
PermissionError
Access forbidden
python
undefined
合理使用Python内置的异常类型,并根据需要添加上下文。
失败类型异常示例
无效输入
ValueError
参数值错误
类型错误
TypeError
期望字符串,实际传入整数
项缺失
KeyError
字典键不存在
操作失败
RuntimeError
服务不可用
超时
TimeoutError
操作耗时过长
文件未找到
FileNotFoundError
路径不存在
权限拒绝
PermissionError
访问被禁止
python
undefined

Good: 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")
undefined
raise Exception("Invalid parameter")
undefined

Advanced 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)
undefined
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)
undefined

Pattern 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 e

Pattern 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()), )
undefined
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()), )
undefined

Pattern 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

最佳实践总结

  1. Validate early - Check inputs before expensive operations
  2. Use specific exceptions -
    ValueError
    ,
    TypeError
    , not generic
    Exception
  3. Include context - Messages should explain what, why, and how to fix
  4. Convert types at boundaries - Parse strings to enums/domain types early
  5. Chain exceptions - Use
    raise ... from e
    to preserve debug info
  6. Handle partial failures - Don't abort batches on single item errors
  7. Use Pydantic - For complex input validation with structured errors
  8. Document failure modes - Docstrings should list possible exceptions
  9. Log with context - Include IDs, counts, and other debugging info
  10. Test error paths - Verify exceptions are raised correctly
  1. 尽早验证 - 在执行昂贵操作前检查输入
  2. 使用特定异常 - 使用
    ValueError
    TypeError
    等,而非通用的
    Exception
  3. 包含上下文 - 错误消息应说明失败内容、原因及修复方法
  4. 在边界转换类型 - 尽早将字符串解析为枚举/领域类型
  5. 链式传递异常 - 使用
    raise ... from e
    保留调试信息
  6. 处理部分失败 - 不要因单个项错误终止批次
  7. 使用Pydantic - 用于复杂输入验证,生成结构化错误
  8. 记录失败模式 - 文档字符串应列出可能抛出的异常
  9. 带上下文日志 - 包含ID、计数等调试信息
  10. 测试错误路径 - 验证异常是否被正确抛出