Python 异常处理深度解析

Python基础 2026-04-20 18
预计阅读时间:33 分钟

一、异常的本质:程序员的"安全气囊" 异常不是错误,而是程序执行过程中遇到的"异常情况"。理解异常处理的关键在于:它不是用来掩盖bug的遮羞布,而是让程序在遇到意外时能够优雅降级的保护机制。

# 没有异常处理的世界:一点小问题就让整个程序崩溃
def divide_numbers(a, b):
    return a / b

result = divide_numbers(10, 0)  # ZeroDivisionError,程序直接崩溃
print("这行永远不会执行")

二、异常处理基础语法 2.1 try-except 核心结构

# 基础结构
try:
    # 可能抛出异常的代码
    result = 10 / 0
except ZeroDivisionError:
    # 处理特定异常
    print("不能除以零!")

# 捕获多种异常
try:
    value = int(input("请输入数字: "))
    result = 100 / value
except ValueError:
    print("请输入有效的数字!")
except ZeroDivisionError:
    print("不能除以零!")
except (TypeError, KeyError) as e:  # 一次捕获多种异常
    print(f"类型或键错误: {e}")

2.2 完整的异常处理结构

def comprehensive_example(filename):
    """展示完整的异常处理流程"""
    file = None
    try:
        # 尝试执行可能出错的代码
        print("try: 开始执行")
        file = open(filename, 'r', encoding='utf-8')
        content = file.read()
        number = int(content.strip())
        result = 100 / number

    except FileNotFoundError as e:
        # 处理文件不存在的情况
        print(f"except: 文件不存在 - {e}")
        print(f"   错误类型: {type(e).__name__}")
        print(f"   错误参数: {e.args}")

    except ValueError as e:
        # 处理数据转换错误
        print(f"except: 数据格式错误 - {e}")

    except ZeroDivisionError as e:
        # 处理除以零的情况
        print(f"except: 数学错误 - {e}")

    except Exception as e:
        # 捕获所有其他异常(不推荐轻易使用)
        print(f"except: 未知错误 - {e}")

    else:
        # 没有异常发生时执行
        print(f"else: 一切顺利,结果是 {result}")

    finally:
        # 无论如何都会执行(清理工作)
        print("finally: 执行清理工作")
        if file:
            file.close()
            print("   文件已关闭")

    print("函数执行完毕")

# 测试不同情况
comprehensive_example("存在且内容为数字.txt")
comprehensive_example("不存在的文件.txt")

三、异常对象详解 3.1 异常对象的属性和方法

try:
    # 故意触发一个复杂异常
    d = {'key': 'value'}
    value = int(d['nonexistent_key'])
except Exception as e:
    print(f"异常类型: {type(e)}")
    print(f"异常类型名称: {type(e).__name__}")
    print(f"异常信息: {str(e)}")
    print(f"异常参数: {e.args}")

    # 获取完整的调用栈信息
    import traceback
    print("\n完整堆栈跟踪:")
    traceback.print_exc()

    # 将堆栈信息保存为字符串
    stack_str = traceback.format_exc()

3.2 Python 异常层次结构

# Python 3 的异常继承树(部分)
"""
BaseException
 ├── SystemExit          # sys.exit() 触发
 ├── KeyboardInterrupt   # Ctrl+C
 ├── GeneratorExit       # 生成器关闭
 └── Exception           # 所有常规异常的基类
      ├── StopIteration       # 迭代器结束
      ├── ArithmeticError     # 算术错误
      │    ├── ZeroDivisionError
      │    └── OverflowError
      ├── LookupError         # 查找错误
      │    ├── IndexError     # 序列索引越界
      │    └── KeyError       # 字典键不存在
      ├── OSError             # 系统错误
      │    ├── FileNotFoundError
      │    ├── PermissionError
      │    └── TimeoutError
      ├── ValueError          # 值错误
      │    └── UnicodeError
      ├── TypeError           # 类型错误
      └── RuntimeError        # 运行时错误
"""

# 演示异常继承关系
def check_exception_hierarchy():
    exceptions = [
        ZeroDivisionError,
        KeyError,
        FileNotFoundError,
        ValueError,
        TypeError
    ]

    for exc in exceptions:
        print(f"{exc.__name__} 继承自:")
        for base in exc.__mro__:  # Method Resolution Order
            if base != object:
                print(f"  -> {base.__name__}")

check_exception_hierarchy()

四、高级异常处理技巧 4.1 异常的链式传递

# Python 3 支持异常链,保留完整的错误上下文
def read_config(filename):
    """演示异常的链式传递"""
    try:
        with open(filename, 'r', encoding='utf-8') as f:
            return f.read()
    except FileNotFoundError as e:
        # 方式1: 显式链式异常 (raise ... from ...)
        raise ValueError(f"配置文件 {filename} 不存在") from e

def parse_config(config_text):
    """解析配置,可能抛出异常"""
    try:
        config = {}
        for line in config_text.split('\n'):
            if '=' in line:
                key, value = line.split('=')
                config[key.strip()] = int(value.strip())
        return config
    except ValueError as e:
        # 方式2: 隐式链式异常
        raise RuntimeError("配置格式错误,期望 key=数字")

# 测试异常链
try:
    config_text = read_config("nonexistent.ini")
    config = parse_config(config_text)
except ValueError as e:
    print(f"捕获到 ValueError: {e}")
    print(f"原始异常: {e.__cause__}")  # 访问原始异常
except RuntimeError as e:
    print(f"捕获到 RuntimeError: {e}")
    print(f"上下文: {e.__context__}")  # 隐式链式

# 查看完整异常链
def print_exception_chain(exc):
    """递归打印异常链"""
    if exc.__cause__:
        print(f"{type(exc).__name__}: {exc}")
        print("  由...引起")
        print_exception_chain(exc.__cause__)
    elif exc.__context__:
        print(f"{type(exc).__name__}: {exc}")
        print("  在处理...时发生")
        print_exception_chain(exc.__context__)
    else:
        print(f"{type(exc).__name__}: {exc}")

4.2 异常处理中的 else 妙用

# finally 确保资源正确释放
def demonstrate_finally():
    """演示 finally 的各种情况"""

    def case1_normal():
        """正常执行"""
        try:
            print("  执行 try")
            return "try 返回值"
        finally:
            print("  执行 finally")
            # 注意:finally 中的 return 会覆盖 try 中的 return

    def case2_exception():
        """发生异常"""
        try:
            print("  执行 try")
            1 / 0
        finally:
            print("  执行 finally")
            # 异常会被传播,但 finally 仍会执行

    def case3_break():
        """包含 break"""
        for i in range(3):
            try:
                print(f"  循环 {i}")
                if i == 1:
                    break
            finally:
                print(f"    finally {i}")

    print("情况1 - 正常执行:")
    result = case1_normal()
    print(f"  返回值: {result}")

    print("\n情况2 - 发生异常:")
    try:
        case2_exception()
    except ZeroDivisionError:
        print("  捕获到异常")

    print("\n情况3 - 包含 break:")
    case3_break()

demonstrate_finally()

# 实用示例:确保文件正确关闭(虽然 with 语句更好)
def legacy_file_handling():
    f = None
    try:
        f = open('test.txt', 'w', encoding='utf-8')
        f.write("一些数据\n")
        # 可能抛出异常的代码
        raise RuntimeError("写入过程中出错")
    except RuntimeError as e:
        print(f"处理错误: {e}")
    finally:
        if f:
            print("关闭文件")
            f.close()

4.3 finally 的资源管理

# finally 确保资源正确释放
def demonstrate_finally():
    """演示 finally 的各种情况"""

    def case1_normal():
        """正常执行"""
        try:
            print("  执行 try")
            return "try 返回值"
        finally:
            print("  执行 finally")
            # 注意:finally 中的 return 会覆盖 try 中的 return

    def case2_exception():
        """发生异常"""
        try:
            print("  执行 try")
            1 / 0
        finally:
            print("  执行 finally")
            # 异常会被传播,但 finally 仍会执行

    def case3_break():
        """包含 break"""
        for i in range(3):
            try:
                print(f"  循环 {i}")
                if i == 1:
                    break
            finally:
                print(f"    finally {i}")

    print("情况1 - 正常执行:")
    result = case1_normal()
    print(f"  返回值: {result}")

    print("\n情况2 - 发生异常:")
    try:
        case2_exception()
    except ZeroDivisionError:
        print("  捕获到异常")

    print("\n情况3 - 包含 break:")
    case3_break()

demonstrate_finally()

# 实用示例:确保文件正确关闭(虽然 with 语句更好)
def legacy_file_handling():
    f = None
    try:
        f = open('test.txt', 'w', encoding='utf-8')
        f.write("一些数据\n")
        # 可能抛出异常的代码
        raise RuntimeError("写入过程中出错")
    except RuntimeError as e:
        print(f"处理错误: {e}")
    finally:
        if f:
            print("关闭文件")
            f.close()

五、自定义异常 5.1 创建自定义异常类

基础自定义异常

class ValidationError(Exception): """数据验证错误的基类""" pass

class InputTooLongError(ValidationError): """输入过长异常""" def init(self, field_name, max_length, actual_length): self.field_name = field_name self.max_length = max_length self.actual_length = actual_length super().init( f"{field_name} 长度 {actual_length} 超过最大限制 {max_length}" )

class InvalidFormatError(ValidationError): """格式错误异常""" def init(self, field_name, expected_format, actual_value): self.field_name = field_name self.expected_format = expected_format self.actual_value = actual_value super().init( f"{field_name} 期望格式 '{expected_format}',实际值 '{actual_value}'" )

业务逻辑异常

class BusinessError(Exception): """业务逻辑异常基类"""

def __init__(self, message, code=None, details=None):
    self.message = message
    self.code = code
    self.details = details or {}
    super().__init__(message)

def to_dict(self):
    """转换为字典,便于 API 返回"""
    return {
        'error': self.__class__.__name__,
        'message': self.message,
        'code': self.code,
        'details': self.details
    }

class InsufficientBalanceError(BusinessError): """余额不足异常""" def init(self, account_id, required, available): super().init( message=f"账户 {account_id} 余额不足", code='INSUFFICIENT_BALANCE', details={ 'account_id': account_id, 'required': required, 'available': available, 'shortage': required - available } )

使用自定义异常

def validate_username(username): """验证用户名""" max_len = 20 if len(username) > max_len: raise InputTooLongError('username', max_len, len(username)) if not username.isalnum(): raise InvalidFormatError('username', '字母和数字', username) return True

def transfer_money(from_account, to_account, amount): """转账业务""" # 模拟账户余额查询 balance = 1000 if amount > balance: raise InsufficientBalanceError(from_account, amount, balance) # 执行转账... return True

测试自定义异常

try: validate_username("this_username_is_way_too_long_for_our_system") except ValidationError as e: print(f"验证失败: {e}") if isinstance(e, InputTooLongError): print(f" 字段: {e.field_name}, 最大长度: {e.max_length}")

try: transfer_money("ACC123", "ACC456", 2000) except BusinessError as e: error_info = e.to_dict() print(f"业务错误: {error_info}") if error_info['code'] == 'INSUFFICIENT_BALANCE': shortage = error_info['details']['shortage'] print(f" 还需 {shortage} 元") 5.2 异常枚举模式

from enum import Enum

class ErrorCode(Enum):
    """错误码枚举"""
    # 系统错误 (1000-1999)
    SYSTEM_ERROR = 1000
    DATABASE_ERROR = 1001
    NETWORK_ERROR = 1002

    # 用户错误 (2000-2999)
    USER_NOT_FOUND = 2000
    INVALID_PASSWORD = 2001
    ACCOUNT_LOCKED = 2002

    # 权限错误 (3000-3999)
    PERMISSION_DENIED = 3000
    TOKEN_EXPIRED = 3001

class AppException(Exception):
    """应用程序统一异常"""

    # 预定义错误码与HTTP状态码的映射
    HTTP_STATUS_MAP = {
        ErrorCode.SYSTEM_ERROR: 500,
        ErrorCode.USER_NOT_FOUND: 404,
        ErrorCode.PERMISSION_DENIED: 403,
    }

    def __init__(self, error_code: ErrorCode, message: str = None, **kwargs):
        self.error_code = error_code
        self.message = message or error_code.name
        self.details = kwargs
        self.http_status = self.HTTP_STATUS_MAP.get(error_code, 400)
        super().__init__(self.message)

    def __str__(self):
        return f"[{self.error_code.value}] {self.message}"

# 使用示例
def authenticate(username, password):
    """用户认证"""
    if username != "admin":
        raise AppException(
            ErrorCode.USER_NOT_FOUND,
            f"用户 '{username}' 不存在",
            attempted_username=username
        )
    if password != "secret":
        raise AppException(
            ErrorCode.INVALID_PASSWORD,
            "密码错误",
            remaining_attempts=2
        )
    return {"token": "abc123"}

try:
    authenticate("user123", "wrong")
except AppException as e:
    print(f"错误: {e}")
    print(f"  HTTP状态: {e.http_status}")
    print(f"  错误码: {e.error_code.value}")
    print(f"  详情: {e.details}")

六、上下文管理器与异常 6.1 理解 with 语句的异常处理

# with 语句的内部机制
class ManagedResource:
    """自定义上下文管理器,展示异常处理"""

    def __init__(self, name):
        self.name = name
        print(f"初始化 {self.name}")

    def __enter__(self):
        print(f"进入 {self.name} 的上下文")
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        """
        退出方法接收异常信息
        exc_type: 异常类型
        exc_val: 异常值
        exc_tb: 追踪信息
        返回 True 可以抑制异常
        """
        print(f"退出 {self.name} 的上下文")
        if exc_type:
            print(f"  发生了异常: {exc_type.__name__}: {exc_val}")
            # 返回 True 表示异常已处理,不会继续传播
            # 返回 False 或 None 表示异常继续传播
            return False
        print("  没有异常发生")
        return False

    def do_something(self, fail=False):
        print(f"  在 {self.name} 中执行操作")
        if fail:
            raise ValueError(f"{self.name} 中发生错误")

# 测试不同情况
print("=== 情况1: 正常执行 ===")
with ManagedResource("Resource1") as res:
    res.do_something(fail=False)

print("\n=== 情况2: 发生异常 ===")
try:
    with ManagedResource("Resource2") as res:
        res.do_something(fail=True)
except ValueError as e:
    print(f"外部捕获异常: {e}")

print("\n=== 情况3: 抑制异常 ===")
class SuppressingResource(ManagedResource):
    def __exit__(self, exc_type, exc_val, exc_tb):
        print(f"退出 {self.name} 的上下文(抑制模式)")
        if exc_type:
            print(f"  抑制异常: {exc_type.__name__}")
            return True  # 抑制异常传播
        return False

with SuppressingResource("Resource3") as res:
    res.do_something(fail=True)
    print("  这行不会执行")
print("异常被抑制,程序继续执行")

6.2 contextlib 实用工具

from contextlib import contextmanager, suppress, ExitStack
import os

# 1. @contextmanager 装饰器 - 快速创建上下文管理器
@contextmanager
def temporary_file(content):
    """创建临时文件,自动清理"""
    import tempfile

    # 创建临时文件
    fd, path = tempfile.mkstemp()
    try:
        with os.fdopen(fd, 'w') as f:
            f.write(content)
        print(f"创建临时文件: {path}")
        yield path  # 提供资源
    finally:
        # 清理工作
        print(f"删除临时文件: {path}")
        os.unlink(path)

with temporary_file("Hello, World!") as filepath:
    with open(filepath, 'r') as f:
        print(f"读取内容: {f.read()}")

# 2. suppress - 优雅地忽略特定异常
def read_number_from_file(filename):
    """从文件读取数字,文件不存在或格式错误时返回 None"""
    with suppress(FileNotFoundError, ValueError):
        with open(filename, 'r') as f:
            return int(f.read().strip())
    return None

# 对比传统写法
def read_number_traditional(filename):
    try:
        with open(filename, 'r') as f:
            return int(f.read().strip())
    except (FileNotFoundError, ValueError):
        return None

# 3. ExitStack - 动态管理多个上下文
def process_multiple_files(filenames):
    """动态处理多个文件"""
    with ExitStack() as stack:
        files = []
        for name in filenames:
            try:
                f = stack.enter_context(open(name, 'r', encoding='utf-8'))
                files.append(f)
            except FileNotFoundError:
                print(f"文件 {name} 不存在,跳过")

        # 处理所有成功打开的文件
        for f in files:
            print(f"处理文件: {f.name}")
            # 即使某个文件处理出错,ExitStack 也会确保所有文件正确关闭

# 4. redirect_stdout - 临时重定向输出
from contextlib import redirect_stdout, redirect_stderr
import io

def capture_output(func, *args, **kwargs):
    """捕获函数的打印输出"""
    stdout_capture = io.StringIO()
    stderr_capture = io.StringIO()

    with redirect_stdout(stdout_capture), redirect_stderr(stderr_capture):
        result = func(*args, **kwargs)

    return {
        'result': result,
        'stdout': stdout_capture.getvalue(),
        'stderr': stderr_capture.getvalue()
    }

def noisy_function():
    print("这是标准输出")
    import sys
    print("这是错误输出", file=sys.stderr)
    return 42

output = capture_output(noisy_function)
print(f"捕获的输出: {output}")

七、异常处理最佳实践 7.1 异常处理的反模式

# 反模式 1: 空的 except 块(吞掉异常)
def bad_practice_1():
    try:
        important_operation()
    except:  # 危险!捕获所有异常,包括 KeyboardInterrupt
        pass  # 静默失败,难以调试

# 反模式 2: 捕获 Exception 但不处理
def bad_practice_2():
    try:
        risky_operation()
    except Exception as e:
        print(f"错误: {e}")  # 仅打印,然后继续执行
        # 可能导致程序状态不一致

# 反模式 3: 过度使用异常处理控制流程
def bad_practice_3(data):
    """用异常处理来代替条件判断"""
    try:
        return data['key']
    except (TypeError, KeyError):
        return None
    # 应该先检查类型和键是否存在

# 反模式 4: 在 finally 中使用 return
def bad_practice_4():
    try:
        return 1
    finally:
        return 2  # 永远返回 2,覆盖 try 中的返回值

# 反模式 5: 异常处理范围过大
def bad_practice_5():
    try:
        step1()
        step2()  # 如果这里出错,不知道是哪一步的问题
        step3()
        step4()
    except Exception as e:
        print(f"某处出错了: {e}")

# 正确做法:精确捕获,最小化 try 块范围
def good_practice_5():
    try:
        step1()
    except SpecificError1 as e:
        handle_error1(e)

    try:
        step2()
    except SpecificError2 as e:
        handle_error2(e)

7.2 异常处理最佳实践模式

import logging
from functools import wraps
import time

# 1. 异常日志记录装饰器
def log_exceptions(logger=None):
    """装饰器:记录函数异常但不中断程序"""
    if logger is None:
        logger = logging.getLogger(__name__)

    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            try:
                return func(*args, **kwargs)
            except Exception as e:
                logger.error(
                    f"函数 {func.__name__} 执行失败",
                    exc_info=True,
                    extra={
                        'args': args,
                        'kwargs': kwargs
                    }
                )
                return None  # 或重新抛出
        return wrapper
    return decorator

# 2. 重试机制
def retry_on_exception(max_attempts=3, delay=1, exceptions=(Exception,)):
    """装饰器:失败时自动重试"""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            last_exception = None

            for attempt in range(max_attempts):
                try:
                    return func(*args, **kwargs)
                except exceptions as e:
                    last_exception = e
                    if attempt < max_attempts - 1:
                        print(f"尝试 {attempt + 1} 失败,{delay}秒后重试...")
                        time.sleep(delay)
                    else:
                        print(f"所有 {max_attempts} 次尝试均失败")

            # 所有重试都失败
            raise last_exception

        return wrapper
    return decorator

@retry_on_exception(max_attempts=3, delay=1, exceptions=(ConnectionError, TimeoutError))
def unstable_network_call():
    """不稳定的网络调用"""
    import random
    if random.random() < 0.7:
        raise ConnectionError("网络连接失败")
    return "成功"

# 3. 异常转换为业务结果(Either 模式)
from typing import Union, Optional, Generic, TypeVar

T = TypeVar('T')
E = TypeVar('E')

class Result(Generic[T, E]):
    """表示可能成功或失败的结果"""

    def __init__(self, value: Optional[T] = None, error: Optional[E] = None):
        self.value = value
        self.error = error
        self.is_success = error is None

    @classmethod
    def success(cls, value: T):
        return cls(value=value)

    @classmethod
    def failure(cls, error: E):
        return cls(error=error)

    def map(self, func):
        """如果成功,对值应用函数"""
        if self.is_success:
            try:
                return Result.success(func(self.value))
            except Exception as e:
                return Result.failure(e)
        return self

    def unwrap_or(self, default: T) -> T:
        """获取值,如果失败则返回默认值"""
        return self.value if self.is_success else default

    def __repr__(self):
        if self.is_success:
            return f"Success({self.value})"
        return f"Failure({self.error})"

# 使用 Result 模式
def safe_divide(a: Union[int, float], b: Union[int, float]) -> Result[float, str]:
    """安全的除法,返回 Result 对象"""
    try:
        if b == 0:
            return Result.failure("除数不能为零")
        return Result.success(a / b)
    except TypeError as e:
        return Result.failure(f"类型错误: {e}")

# 测试
results = [
    safe_divide(10, 2),
    safe_divide(10, 0),
    safe_divide("10", 2)
]

for result in results:
    print(result)
    if result.is_success:
        print(f"  计算结果: {result.value}")
    else:
        print(f"  错误信息: {result.error}")

# 4. 异常处理的状态机模式
class ConnectionState:
    """使用状态模式处理不同状态下的异常"""

    def handle_error(self, error):
        raise NotImplementedError

class ConnectedState(ConnectionState):
    def handle_error(self, error):
        if isinstance(error, TimeoutError):
            return ReconnectingState()
        return self

class ReconnectingState(ConnectionState):
    def __init__(self, attempts=0):
        self.attempts = attempts

    def handle_error(self, error):
        if self.attempts < 3:
            return ReconnectingState(self.attempts + 1)
        return DisconnectedState()

class DisconnectedState(ConnectionState):
    def handle_error(self, error):
        # 不再处理错误,让上层处理
        raise error

class NetworkConnection:
    def __init__(self):
        self.state = DisconnectedState()

    def connect(self):
        try:
            # 尝试连接
            self._do_connect()
            self.state = ConnectedState()
        except Exception as e:
            self.state = self.state.handle_error(e)

    def _do_connect(self):
        # 实际连接逻辑
        pass

7.3 生产环境的异常处理策略

import sys
import traceback
import logging
from datetime import datetime
import json

class ProductionExceptionHandler:
    """生产环境异常处理框架"""

    def __init__(self, app_name="MyApp"):
        self.app_name = app_name
        self.setup_logging()
        self.error_count = 0
        self.error_threshold = 10

    def setup_logging(self):
        """配置结构化日志"""
        self.logger = logging.getLogger(self.app_name)
        self.logger.setLevel(logging.ERROR)

        # JSON 格式便于日志分析
        handler = logging.StreamHandler()
        formatter = logging.Formatter(
            '{"time": "%(asctime)s", "level": "%(levelname)s", '
            '"module": "%(module)s", "message": %(message)s}'
        )
        handler.setFormatter(formatter)
        self.logger.addHandler(handler)

    def handle_exception(self, exc_type, exc_value, exc_traceback):
        """全局异常处理器"""
        # 忽略 KeyboardInterrupt(用户中断)
        if issubclass(exc_type, KeyboardInterrupt):
            sys.__excepthook__(exc_type, exc_value, exc_traceback)
            return

        # 统计错误
        self.error_count += 1

        # 结构化错误信息
        error_info = {
            "app": self.app_name,
            "timestamp": datetime.now().isoformat(),
            "error_type": exc_type.__name__,
            "error_message": str(exc_value),
            "traceback": traceback.format_tb(exc_traceback),
        }

        # 记录错误
        self.logger.error(json.dumps(error_info, ensure_ascii=False))

        # 错误阈值告警
        if self.error_count >= self.error_threshold:
            self.send_alert(f"错误数量达到阈值: {self.error_count}")
            self.error_count = 0

        # 致命错误处理
        if issubclass(exc_type, (MemoryError, SystemError)):
            self.graceful_shutdown()

    def send_alert(self, message):
        """发送告警"""
        print(f"[ALERT] {message}")
        # 实际项目中可能发送邮件、短信或调用监控API

    def graceful_shutdown(self):
        """优雅关闭"""
        print("执行优雅关闭...")
        # 清理资源、保存状态
        sys.exit(1)

# 设置全局异常处理器
def setup_global_exception_handler():
    """在应用程序入口设置"""
    handler = ProductionExceptionHandler()
    sys.excepthook = handler.handle_exception

# 在 Flask/Django 等框架中的异常处理
class WebAppExceptionHandler:
    """Web应用异常处理"""

    ERROR_RESPONSES = {
        ValueError: (400, "请求参数错误"),
        PermissionError: (403, "权限不足"),
        FileNotFoundError: (404, "资源不存在"),
        TimeoutError: (504, "请求超时"),
    }

    @classmethod
    def handle(cls, error):
        """返回适合Web框架的错误响应"""
        status_code, message = cls.ERROR_RESPONSES.get(
            type(error), 
            (500, "服务器内部错误")
        )

        # 生产环境不暴露详细错误信息
        if status_code == 500:
            # 记录详细错误
            logging.error(f"内部错误", exc_info=error)
            detail = "请联系管理员"
        else:
            detail = str(error)

        return {
            "error": message,
            "detail": detail,
            "status_code": status_code
        }

# 使用示例
def api_endpoint():
    try:
        user_id = int(request.args.get('id'))
        user = find_user(user_id)
        return user.to_dict()
    except Exception as e:
        error_response = WebAppExceptionHandler.handle(e)
        return jsonify(error_response), error_response['status_code']

八、性能考量与调试技巧 8.1 异常的性能影响

import pdb
import sys
import traceback
from inspect import currentframe, getframeinfo

# 1. 获取详细异常信息
def get_exception_details():
    """获取当前异常的详细信息"""
    exc_type, exc_value, exc_traceback = sys.exc_info()

    if exc_type is None:
        return None

    # 获取调用栈
    tb_list = traceback.extract_tb(exc_traceback)

    details = {
        'type': exc_type.__name__,
        'message': str(exc_value),
        'module': exc_type.__module__,
        'traceback': []
    }

    for filename, lineno, funcname, text in tb_list:
        details['traceback'].append({
            'file': filename,
            'line': lineno,
            'function': funcname,
            'code': text
        })

    return details

# 2. 条件断点
def conditional_debugging():
    """仅在特定异常发生时进入调试器"""
    try:
        # 有问题的代码
        x = 1 / 0
    except ZeroDivisionError as e:
        # 只在特定条件下进入调试器
        frame = currentframe()
        filename = getframeinfo(frame).filename

        if 'test' in filename:
            print("进入调试模式...")
            pdb.set_trace()  # 进入交互式调试

        # 重新抛出
        raise

# 3. 异常钩子 - 在异常发生时自动收集信息
class ExceptionDebugger:
    """异常调试辅助类"""

    def __init__(self, enable_auto_debug=False):
        self.enable_auto_debug = enable_auto_debug
        self.exception_history = []

    def __enter__(self):
        self.old_excepthook = sys.excepthook
        sys.excepthook = self.debug_excepthook
        return self

    def __exit__(self, *args):
        sys.excepthook = self.old_excepthook

    def debug_excepthook(self, exc_type, exc_value, exc_traceback):
        """自定义异常钩子"""
        # 收集异常信息
        details = self.collect_context(exc_type, exc_value, exc_traceback)
        self.exception_history.append(details)

        # 打印增强的错误信息
        self.print_enhanced_error(details)

        # 自动进入调试器
        if self.enable_auto_debug:
            print("\n自动进入调试器...")
            pdb.post_mortem(exc_traceback)

        # 调用原始钩子
        if self.old_excepthook != sys.__excepthook__:
            self.old_excepthook(exc_type, exc_value, exc_traceback)

    def collect_context(self, exc_type, exc_value, exc_traceback):
        """收集异常上下文信息"""
        import inspect

        context = {
            'exception': {
                'type': exc_type.__name__,
                'value': str(exc_value)
            },
            'locals': {},
            'globals': {}
        }

        # 获取异常发生时的局部变量
        tb = exc_traceback
        while tb:
            frame = tb.tb_frame
            context['locals'][frame.f_code.co_name] = {
                k: repr(v)[:100] for k, v in frame.f_locals.items()
            }
            tb = tb.tb_next

        return context

    def print_enhanced_error(self, details):
        """打印增强的错误信息"""
        print("\n" + "="*60)
        print(f"异常类型: {details['exception']['type']}")
        print(f"异常信息: {details['exception']['value']}")
        print("\n异常发生时的局部变量:")
        for func_name, locals_dict in details['locals'].items():
            print(f"  函数 {func_name}:")
            for var, val in list(locals_dict.items())[:5]:
                print(f"    {var} = {val}")
        print("="*60)

# 使用示例
def problematic_function(x, y):
    z = x * 2
    result = z / y
    return result

with ExceptionDebugger(enable_auto_debug=False) as debugger:
    try:
        problematic_function(5, 0)
    except ZeroDivisionError:
        print("捕获到异常,查看历史记录...")
        if debugger.exception_history:
            last_error = debugger.exception_history[-1]
            print(f"最后异常: {last_error['exception']}")

8.2 异常调试技巧 import pdb import sys import traceback from inspect import currentframe, getframeinfo

1. 获取详细异常信息

def get_exception_details(): """获取当前异常的详细信息""" exc_type, exc_value, exc_traceback = sys.exc_info()

if exc_type is None:
    return None

# 获取调用栈
tb_list = traceback.extract_tb(exc_traceback)

details = {
    'type': exc_type.__name__,
    'message': str(exc_value),
    'module': exc_type.__module__,
    'traceback': []
}

for filename, lineno, funcname, text in tb_list:
    details['traceback'].append({
        'file': filename,
        'line': lineno,
        'function': funcname,
        'code': text
    })

return details

2. 条件断点

def conditional_debugging(): """仅在特定异常发生时进入调试器""" try: # 有问题的代码 x = 1 / 0 except ZeroDivisionError as e: # 只在特定条件下进入调试器 frame = currentframe() filename = getframeinfo(frame).filename

    if 'test' in filename:
        print("进入调试模式...")
        pdb.set_trace()  # 进入交互式调试

    # 重新抛出
    raise

3. 异常钩子 - 在异常发生时自动收集信息

class ExceptionDebugger: """异常调试辅助类"""

def __init__(self, enable_auto_debug=False):
    self.enable_auto_debug = enable_auto_debug
    self.exception_history = []

def __enter__(self):
    self.old_excepthook = sys.excepthook
    sys.excepthook = self.debug_excepthook
    return self

def __exit__(self, *args):
    sys.excepthook = self.old_excepthook

def debug_excepthook(self, exc_type, exc_value, exc_traceback):
    """自定义异常钩子"""
    # 收集异常信息
    details = self.collect_context(exc_type, exc_value, exc_traceback)
    self.exception_history.append(details)

    # 打印增强的错误信息
    self.print_enhanced_error(details)

    # 自动进入调试器
    if self.enable_auto_debug:
        print("\n自动进入调试器...")
        pdb.post_mortem(exc_traceback)

    # 调用原始钩子
    if self.old_excepthook != sys.__excepthook__:
        self.old_excepthook(exc_type, exc_value, exc_traceback)

def collect_context(self, exc_type, exc_value, exc_traceback):
    """收集异常上下文信息"""
    import inspect

    context = {
        'exception': {
            'type': exc_type.__name__,
            'value': str(exc_value)
        },
        'locals': {},
        'globals': {}
    }

    # 获取异常发生时的局部变量
    tb = exc_traceback
    while tb:
        frame = tb.tb_frame
        context['locals'][frame.f_code.co_name] = {
            k: repr(v)[:100] for k, v in frame.f_locals.items()
        }
        tb = tb.tb_next

    return context

def print_enhanced_error(self, details):
    """打印增强的错误信息"""
    print("\n" + "="*60)
    print(f"异常类型: {details['exception']['type']}")
    print(f"异常信息: {details['exception']['value']}")
    print("\n异常发生时的局部变量:")
    for func_name, locals_dict in details['locals'].items():
        print(f"  函数 {func_name}:")
        for var, val in list(locals_dict.items())[:5]:
            print(f"    {var} = {val}")
    print("="*60)

使用示例

def problematic_function(x, y): z = x * 2 result = z / y return result

with ExceptionDebugger(enable_auto_debug=False) as debugger: try: problematic_function(5, 0) except ZeroDivisionError: print("捕获到异常,查看历史记录...") if debugger.exception_history: last_error = debugger.exception_history[-1] print(f"最后异常: {last_error['exception']}") 九、总结与最佳实践清单 异常处理黄金法则

"""
异常处理最佳实践清单:

1. ✓ 明确捕获范围
   - 捕获具体异常而非 Exception
   - try 块范围尽可能小

2. ✓ 正确处理异常
   - 要么修复问题并恢复
   - 要么转换为用户友好的信息
   - 要么记录后重新抛出

3. ✓ 资源管理
   - 优先使用 with 语句
   - finally 确保资源释放
   - 避免在 __del__ 中清理资源

4. ✓ 自定义异常
   - 继承自 Exception 而非 BaseException
   - 提供有意义的错误信息
   - 包含足够的上下文信息

5. ✓ 异常链
   - 使用 raise ... from 保留原始异常
   - 不要丢失重要的错误上下文

6. ✓ 日志记录
   - 记录异常时的环境信息
   - 使用结构化日志
   - 区分不同级别的错误

7. ✓ 性能意识
   - 异常有性能成本
   - 高频路径使用条件判断
   - 不可预见的错误才用异常

8. ✓ 测试
   - 测试异常情况
   - 验证异常消息和类型
   - 确保清理代码执行
"""

# 异常处理模板
def robust_function_template(param1, param2):
    """健壮函数的模板"""
    # 1. 参数验证(快速失败)
    if not isinstance(param1, int):
        raise TypeError(f"param1 必须是 int,实际为 {type(param1)}")

    # 2. 资源获取
    resource = None
    try:
        # 3. 核心逻辑
        resource = acquire_resource()
        result = process_resource(resource, param2)

    except SpecificError1 as e:
        # 4. 可恢复的错误
        logger.warning(f"遇到可恢复错误: {e}")
        result = fallback_behavior()

    except SpecificError2 as e:
        # 5. 不可恢复但可转换的错误
        logger.error(f"严重错误: {e}", exc_info=True)
        raise BusinessException("操作失败") from e

    finally:
        # 6. 资源清理
        if resource:
            release_resource(resource)

    # 7. 后置条件检查
    assert result is not None, "结果不应为 None"

    return result

记住:异常处理的目标不是让程序永不崩溃,而是在崩溃时留下足够的诊断信息,并在可能的情况下优雅降级。好的异常处理是软件可维护性和健壮性的基石。


本文由 尚先生 原创,转载请注明出处。

📖相关推荐

评论

0
暂无评论,来发表第一条评论吧

发表评论

登录 后发表评论