Python异常处理不是"加个try-except就完事"工作。生产代码中,有时候异常处理策略直接决定了系统的可维护性和调试效率。以下5种模式从基础到高阶,覆盖90%的工程场景。
模式一:精准捕获优于泛化捕获
新手常写except Exception:,这等于关闭所有错误信息。精准捕获让问题暴露得更早。
import requestsfrom requests.exceptions import ConnectionError, Timeout, HTTPErrordef fetch_api_data(url: str) -> dict: """精准捕获不同异常,每种错误有独立的处理逻辑""" try: response = requests.get(url, timeout=5) response.raise_for_status() return response.json() except ConnectionError: logger.error(f"网络连接失败: {url}") raise RetryableError("网络不可用,建议稍后重试") except Timeout: logger.warning(f"请求超时: {url}") raise RetryableError("请求超时,建议缩短时间范围后重试") except HTTPError as e: if e.response.status_code == 429: raise RateLimitError("请求频率超限,请降低调用频率") elif e.response.status_code >= 500: raise RetryableError("服务端暂时不可用") else: raise ClientError(f"客户端错误: {e.response.status_code}") except ValueError: logger.error(f"返回数据不是有效JSON: {url}") raise DataFormatError("服务端返回数据格式异常")
精准捕获的核心原则:异常类型即文档。读代码的人看到except Timeout就知道这里可能超时,不需要翻注释。
模式二:自定义异常体系让错误可追踪
项目规模扩大后,内置异常不够用。建立层次化的自定义异常体系,是中型以上Python项目的标配。
class ProjectError(Exception): """所有业务异常的基类""" def __init__(self, message: str, error_code: str = None, context: dict = None): super().__init__(message) self.error_code = error_code or "UNKNOWN" self.context = context or {} self.timestamp = datetime.utcnow().isoformat() def to_dict(self) -> dict: return { "error_code": self.error_code, "message": str(self), "timestamp": self.timestamp, "context": self.context }class ValidationError(ProjectError): """输入验证失败""" passclass NotFoundError(ProjectError): """资源不存在""" passclass ConflictError(ProjectError): """资源冲突(如重复创建)""" passclass RetryableError(ProjectError): """可以重试的临时错误""" passclass PermanentError(ProjectError): """不可重试的永久性错误""" passdef get_user(user_id: str) -> User: user = db.query(User).filter_by(id=user_id).first() if not user: raise NotFoundError( f"用户不存在: {user_id}", error_code="USER_NOT_FOUND", context={"user_id": user_id, "query_table": "users"} ) return user@app.errorhandler(ProjectError)def handle_project_error(error: ProjectError): return jsonify(error.to_dict()), 400
自定义异常体系的价值:错误码统一、上下文自动携带、日志和监控自动关联。
模式三:上下文管理器实现资源安全释放
文件、锁、连接、事务——这些资源必须成对获取和释放。with语句是Python最优雅的异常安全机制。
from contextlib import contextmanagerimport threadingimport sqlite3@contextmanagerdef transaction(conn: sqlite3.Connection): """事务上下文:成功提交,异常回滚""" try: yield conn conn.commit() logger.info("事务提交成功") except Exception: conn.rollback() logger.error("事务回滚", exc_info=True) raise # 重新抛出,让上层决定如何处理with transaction(db_conn) as conn: conn.execute("INSERT INTO orders VALUES (?, ?)", (order_id, amount)) conn.execute("UPDATE inventory SET stock = stock - ?", (quantity,))@contextmanagerdef acquire_lock(lock: threading.RLock, timeout: float = 10.0): """带超时的锁获取,确保释放""" if not lock.acquire(timeout=timeout): raise TimeoutError(f"获取锁超时: {timeout}秒") try: yield lock finally: lock.release() logger.debug("锁已释放")@contextmanagerdef temp_file(suffix: str = ".tmp"): """创建临时文件,退出时自动删除""" import tempfile path = tempfile.mktemp(suffix=suffix) try: yield path finally: if os.path.exists(path): os.remove(path) logger.debug(f"临时文件已清理: {path}")
上下文管理器的核心:__enter__获取资源,__exit__释放资源。无论中间发生什么异常,释放逻辑都保证执行。
模式四:异常链保留完整调用链路
Python 3的raise ... from ...语法让异常链可追溯。生产环境中,丢失原始异常是调试噩梦。
def parse_config_file(path: str) -> dict: """解析配置文件,保留完整异常链""" try: with open(path, 'r', encoding='utf-8') as f: content = f.read() except FileNotFoundError as e: raise ConfigError(f"配置文件不存在: {path}") from e except UnicodeDecodeError as e: raise ConfigError(f"配置文件编码错误: {path}") try: config = json.loads(content) except json.JSONDecodeError as e: raise ConfigError( f"配置文件JSON格式错误: {path}, 第{e.lineno}行" ) from e required = ["database_url", "api_key", "max_workers"] for field in required: if field not in config: raise ValidationError(f"缺少必填配置项: {field}") return config
异常链的价值:运维人员看到ConfigError知道是配置问题,展开看到JSONDecodeError知道具体是第15行语法错误,再展开看到原始文件路径。三层信息,缺一不可。
模式五:结构化日志与异常监控集成
生产环境中,异常处理不再是"打印到控制台",是"结构化日志+监控告警+链路追踪"的完整闭环。
import structlogfrom functools import wrapsimport tracebacklogger = structlog.get_logger()def monitored(operation_name: str): """监控装饰器:自动记录异常、上报指标""" def decorator(func): @wraps(func) def wrapper(*args, **kwargs): try: result = func(*args, **kwargs) metrics.increment(f"{operation_name}.success") return result except Exception as e: metrics.increment(f"{operation_name}.failure", tags={"error_type": type(e).__name__}) logger.error( f"{operation_name} 失败", operation=operation_name, error_type=type(e).__name__, error_message=str(e), traceback=traceback.format_exc(), args_count=len(args), kwargs_keys=list(kwargs.keys()), ) if isinstance(e, (DatabaseError, NetworkError)): alert.send(f"{operation_name} 严重异常: {str(e)}") raise return wrapper return decorator@monitored("payment_process")def process_payment(order_id: str, amount: Decimal, method: str) -> Receipt: pass
一个容易被忽略的事实:异常处理是性能瓶颈
异常抛出和捕获有开销。在热路径(高频循环)中滥用异常控制流程,可能让代码慢10倍。
import timedef find_user_bad(user_list: list, user_id: str) -> User: for user in user_list: try: if user.id == user_id: return user except AttributeError: continue raise NotFoundError("用户不存在")def find_user_good(user_list: list, user_id: str) -> User: for user in user_list: if hasattr(user, 'id') and user.id == user_id: return user raise NotFoundError("用户不存在")users = [User(id=f"u{i}") for i in range(10000)]
异常处理的工程化原则:精准捕获、自定义体系、资源安全、链式追溯、监控集成。这5种模式是生产代码的异常处理基础设施。写代码时多花10分钟设计异常策略,调试时少花10小时定位问题。