一、异常的本质:程序员的"安全气囊" 异常不是错误,而是程序执行过程中遇到的"异常情况"。理解异常处理的关键在于:它不是用来掩盖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