一、什么是 sys.exc_info()?
sys.exc_info() 是 Python 标准库 sys 模块中的一个函数,用于获取当前正在处理的异常信息。它返回一个包含三个元素的元组,提供关于当前异常的详细信息。
二、exc_info() 的基本用法
1. 返回值结构
import sysdef basic_exc_info(): """exc_info() 的基本用法""" try: # 引发一个异常 1 / 0 except Exception as e: # 获取异常信息 exc_type, exc_value, exc_traceback = sys.exc_info() print(f"异常类型: {exc_type}") print(f"异常值: {exc_value}") print(f"异常类型名称: {exc_type.__name__}") print(f"异常信息: {exc_value}") print(f"回溯对象: {exc_traceback}")basic_exc_info()
2. exc_info() 的三个返回值
import sysimport tracebackdef three_return_values(): """exc_info() 的三个返回值详解""" try: # 引发复杂异常 raise ValueError("这是一个值错误", 100, {"detail": "额外信息"}) except Exception: exc_type, exc_value, exc_traceback = sys.exc_info() print("=== 异常类型 (exc_type) ===") print(f"类型: {exc_type}") print(f"类型名称: {exc_type.__name__}") print(f"类型模块: {exc_type.__module__}") print("\n=== 异常值 (exc_value) ===") print(f"值: {exc_value}") print(f"参数: {exc_value.args}") print(f"字符串表示: {str(exc_value)}") print("\n=== 回溯对象 (exc_traceback) ===") print(f"回溯对象: {exc_traceback}") # 使用 traceback 模块格式化回溯信息 print("\n=== 格式化回溯 ===") tb_lines = traceback.format_tb(exc_traceback) for line in tb_lines: print(line, end='')three_return_values()
三、在不同场景中使用 exc_info()
1. 在异常处理中获取详细信息
import sysimport tracebackdef detailed_exception_info(): """获取详细的异常信息""" def process_data(data): """处理数据的函数""" if not data: raise ValueError("数据不能为空") if not isinstance(data, list): raise TypeError(f"数据必须是列表,得到 {type(data).__name__}") if len(data) < 3: raise IndexError(f"数据长度不足,需要至少3个元素,当前: {len(data)}") return sum(data) / len(data) try: # 调用可能出错的函数 result = process_data([1, 2]) print(f"结果: {result}") except Exception: # 获取完整的异常信息 exc_type, exc_value, exc_tb = sys.exc_info() print("=" * 50) print("异常详细信息:") print("=" * 50) # 基本信息 print(f"异常类型: {exc_type.__name__}") print(f"异常信息: {exc_value}") print(f"异常模块: {exc_type.__module__}") # 异常参数 if exc_value.args: print(f"异常参数: {exc_value.args}") # 获取文件名和行号 tb = exc_tb while tb: filename = tb.tb_frame.f_code.co_filename line_number = tb.tb_lineno function_name = tb.tb_frame.f_code.co_name print(f"\n位置: {filename}") print(f"行号: {line_number}") print(f"函数: {function_name}") tb = tb.tb_next # 完整的堆栈跟踪 print("\n完整堆栈跟踪:") traceback.print_tb(exc_tb)detailed_exception_info()
2. 日志记录中的使用
import sysimport loggingimport tracebackfrom datetime import datetimedef logging_with_exc_info(): """在日志记录中使用 exc_info()""" # 配置日志 logging.basicConfig( level=logging.ERROR, format='%(asctime)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler('error.log'), logging.StreamHandler() ] ) def log_exception_decorator(func): """记录异常的装饰器""" def wrapper(*args, **kwargs): try: return func(*args, **kwargs) except Exception: exc_type, exc_value, exc_tb = sys.exc_info() # 记录详细的异常信息 logging.error( f"函数 {func.__name__} 发生异常\n" f"类型: {exc_type.__name__}\n" f"信息: {exc_value}\n" f"位置: {exc_tb.tb_frame.f_code.co_filename}:{exc_tb.tb_lineno}" ) # 也可以记录完整的堆栈跟踪 # logging.exception("发生异常") # 自动包含堆栈 raise # 重新抛出异常 return wrapper @log_exception_decorator def risky_operation(): """可能失败的操作""" return 10 / 0 try: risky_operation() except ZeroDivisionError: print("捕获到除零错误")# logging_with_exc_info()
3. 异常重新包装
import sysdef exception_wrapping(): """使用 exc_info() 重新包装异常""" class AppError(Exception): """应用错误基类""" pass class DatabaseError(AppError): """数据库错误""" pass def db_operation(): """数据库操作""" # 模拟数据库错误 raise ConnectionError("无法连接到数据库服务器") def process_request(): """处理请求""" try: db_operation() except ConnectionError: # 获取原始异常信息 exc_type, exc_value, exc_tb = sys.exc_info() # 重新包装为应用异常 new_exc = DatabaseError(f"数据库操作失败: {exc_value}") # 保留原始异常信息 new_exc.__cause__ = exc_value new_exc.original_type = exc_type new_exc.original_traceback = exc_tb raise new_exc try: process_request() except DatabaseError as e: print(f"捕获到应用错误: {e}") print(f"原始异常: {e.__cause__}") print(f"原始类型: {e.original_type.__name__}")exception_wrapping()
四、exc_info() 的高级用法
1. 自定义异常上下文管理器
import sysimport tracebackfrom contextlib import contextmanagerdef custom_exception_context(): """自定义异常上下文管理器""" class ExceptionContext: """异常上下文""" def __init__(self): self.exc_type = None self.exc_value = None self.exc_traceback = None self.handled = False def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): if exc_type: self.exc_type = exc_type self.exc_value = exc_val self.exc_traceback = exc_tb self.handled = True return self.handled @contextmanager def capture_exception(): """捕获异常的上下文管理器""" try: yield except Exception: exc_type, exc_value, exc_tb = sys.exc_info() print(f"捕获到异常: {exc_type.__name__}: {exc_value}") print("异常已被上下文管理器捕获") # 不重新抛出,异常被抑制 # 使用自定义上下文 print("=== 使用 ExceptionContext ===") with ExceptionContext() as ctx: result = 10 / 0 if ctx.exc_type: print(f"捕获到异常: {ctx.exc_type.__name__}") print(f"异常信息: {ctx.exc_value}") # 使用上下文管理器捕获异常 print("\n=== 使用 capture_exception ===") with capture_exception(): result = 10 / 0 print("这行不会执行") print("程序继续执行")custom_exception_context()
2. 异常过滤和分类
import sysimport timedef exception_filtering(): """异常过滤和分类""" class ExceptionClassifier: """异常分类器""" @staticmethod def classify(): """分类当前异常""" exc_type, exc_value, exc_tb = sys.exc_info() if exc_type is None: return "无异常" # 根据异常类型分类 if issubclass(exc_type, ValueError): return "值错误" elif issubclass(exc_type, TypeError): return "类型错误" elif issubclass(exc_type, (IOError, OSError)): return "IO错误" elif issubclass(exc_type, (ConnectionError, TimeoutError)): return "网络错误" else: return "其他错误" @staticmethod def get_severity(): """获取异常严重程度""" exc_type, exc_value, exc_tb = sys.exc_info() if exc_type is None: return "none" # 根据异常类型判断严重程度 critical_exceptions = (MemoryError, SystemError, KeyboardInterrupt) error_exceptions = (ValueError, TypeError, KeyError, IndexError) warning_exceptions = (UserWarning, DeprecationWarning) if issubclass(exc_type, critical_exceptions): return "critical" elif issubclass(exc_type, error_exceptions): return "error" elif issubclass(exc_type, warning_exceptions): return "warning" else: return "info" def test_exceptions(): """测试各种异常""" exceptions = [ (ValueError, "值错误测试"), (TypeError, "类型错误测试"), (KeyError, "键错误测试"), (ConnectionError, "连接错误测试"), ] for exc_class, msg in exceptions: try: raise exc_class(msg) except Exception: category = ExceptionClassifier.classify() severity = ExceptionClassifier.get_severity() print(f"{exc_class.__name__}: 分类={category}, 严重程度={severity}") test_exceptions()exception_filtering()
3. 异常监控和统计
import sysimport timeimport threadingfrom collections import defaultdictdef exception_monitoring(): """异常监控和统计""" class ExceptionMonitor: """异常监控器""" def __init__(self): self.stats = defaultdict(lambda: { 'count': 0, 'last_occurrence': None, 'messages': [], 'locations': set() }) self.lock = threading.Lock() def record(self): """记录当前异常""" exc_type, exc_value, exc_tb = sys.exc_info() if exc_type is None: return with self.lock: key = exc_type.__name__ self.stats[key]['count'] += 1 self.stats[key]['last_occurrence'] = time.time() # 记录错误信息(最多10条) msg = str(exc_value) if len(self.stats[key]['messages']) < 10: self.stats[key]['messages'].append(msg) # 记录位置 if exc_tb: filename = exc_tb.tb_frame.f_code.co_filename lineno = exc_tb.tb_lineno self.stats[key]['locations'].add(f"{filename}:{lineno}") def get_report(self): """获取统计报告""" report = {} with self.lock: for name, data in self.stats.items(): report[name] = { 'count': data['count'], 'last_occurrence': time.ctime(data['last_occurrence']) if data['last_occurrence'] else None, 'sample_messages': data['messages'][:5], 'locations': list(data['locations']) } return report def reset(self): """重置统计""" with self.lock: self.stats.clear() # 装饰器版本 def monitored(func): """监控装饰器""" def wrapper(*args, **kwargs): monitor = ExceptionMonitor() try: return func(*args, **kwargs) except Exception: monitor.record() raise return wrapper # 使用监控器 monitor = ExceptionMonitor() def test_operations(): """测试操作""" operations = [ lambda: 1 / 0, lambda: int("abc"), lambda: [1,2,3][10], lambda: {}['key'], lambda: 1 / 0, # 重复异常 ] for op in operations: try: op() except Exception: monitor.record() # 打印报告 print("异常统计报告:") for name, data in monitor.get_report().items(): print(f"\n{name}:") print(f" 发生次数: {data['count']}") print(f" 最后发生: {data['last_occurrence']}") print(f" 错误消息示例: {data['sample_messages'][:2]}") print(f" 发生位置: {data['locations']}") test_operations()exception_monitoring()
五、exc_info() 的注意事项
1. 生命周期和循环引用
import sysimport gcdef lifecycle_notes(): """exc_info() 的生命周期注意事项""" # 注意:exc_info() 返回的回溯对象可能包含循环引用 def memory_issue(): """可能导致内存泄漏的示例""" try: raise ValueError("测试错误") except Exception: exc_type, exc_value, exc_tb = sys.exc_info() # exc_tb 包含对当前帧的引用 # 如果存储了 exc_tb,可能导致循环引用 return exc_tb # 更好的做法:使用 traceback 模块提取需要的信息 # 而不是保存整个回溯对象 def better_approach(): """更好的做法""" try: raise ValueError("测试错误") except Exception: import traceback # 只保存格式化的信息,而不是回溯对象 tb_lines = traceback.format_exc() return tb_lines # 清理异常信息 def clear_exception(): """清理异常信息""" try: raise ValueError("测试") except Exception: exc_type, exc_value, exc_tb = sys.exc_info() # 处理异常... # 清理引用,帮助垃圾回收 del exc_type, exc_value, exc_tb print("注意: exc_info() 返回的对象可能包含循环引用") print("建议: 使用 traceback 模块提取信息而不是保存回溯对象")lifecycle_notes()
2. 线程安全
import sysimport threadingimport timedef thread_safety(): """exc_info() 的线程安全性""" # exc_info() 是线程安全的,返回当前线程的异常信息 results = [] def worker(worker_id): try: # 每个线程有自己的异常信息 if worker_id % 2 == 0: raise ValueError(f"线程 {worker_id} 的值错误") else: raise TypeError(f"线程 {worker_id} 的类型错误") except Exception: # 获取当前线程的异常信息 exc_type, exc_value, exc_tb = sys.exc_info() results.append({ 'thread': worker_id, 'type': exc_type.__name__, 'message': str(exc_value) }) # 创建多个线程 threads = [] for i in range(5): t = threading.Thread(target=worker, args=(i,)) threads.append(t) t.start() for t in threads: t.join() # 打印结果 for result in results: print(f"线程 {result['thread']}: {result['type']} - {result['message']}")thread_safety()
六、总结
sys.exc_info() 要点表格
使用场景总结
最佳实践
优先使用 traceback 模块
及时清理异常信息
try: # codeexcept Exception: exc_type, exc_value, exc_tb = sys.exc_info() # 处理异常 del exc_type, exc_value, exc_tb # 帮助垃圾回收
线程安全
exc_info() 是线程安全的
每个线程有独立的异常信息
在 except 块外使用
exc_info() 只在 except 块内有意义
在 except 块外返回 (None, None, None)
sys.exc_info() 是 Python 中获取异常信息的强大工具,在日志记录、异常包装、错误处理框架等场景中非常有用。正确使用它可以帮助你构建更健壮、更易调试的应用程序。