裸 except: 危险因捕获所有异常(含 KeyboardInterrupt、SystemExit),导致调试困难、资源泄漏;应显式指定异常类型,用 except Exception: 替代,捕获后须重抛或完整记录日志。

为什么 except: 是危险的默认选择
它会捕获所有异常,包括 KeyboardInterrupt(Ctrl+C)、SystemExit(sys.exit() 触发)甚至内存耗尽时的 MemoryError。这些本该让程序中止或交由上层处理的信号,被无声吞掉后,会导致调试困难、资源泄漏、逻辑卡死。
常见现象:脚本在终端按 Ctrl+C 没反应;单元测试因 SystemExit 未抛出而误判通过;服务进程在 OOM 后继续“假运行”。
- 永远显式列出需要处理的异常类型,例如
except ValueError:或except (IOError, OSError): - 若真需兜底,用
except Exception:—— 它排除了BaseException的子类如SystemExit和KeyboardInterrupt - 绝不在生产代码里写裸
except:,CI 流水线应配置pylint规则bare-except报警
except Exception as e: 后不 re-raise 的静默失败
捕获后只打印日志却不重新抛出,会让上游调用方误以为操作成功。尤其在函数返回值无明确错误标识(如只返回 None 或布尔值)时,问题会逐层掩盖。
典型场景:数据库连接函数捕获 ConnectionError 后仅写日志并返回 False,但调用方未检查返回值,继续执行后续 SQL,最终报错位置远离真实原因。
立即学习“Python免费学习笔记(深入)”;
- 捕获后若无法本地恢复,应至少保留原始 traceback:
raise(原样重抛)或raise e from None(清除旧上下文) - 若必须吞异常(如清理临时文件),务必记录完整异常信息:
logging.exception("cleanup failed") - 避免用
str(e)拼接日志——它丢弃类型、traceback 和 cause 链
宽泛捕获掩盖了真正的编程错误
比如把 KeyError 和 TypeError 一起写进 except (KeyError, TypeError):,表面看是“兼容性处理”,实则混淆了两类根本不同的问题:一个是数据缺失(应检查输入或加默认值),一个是类型误用(应修正逻辑或加类型断言)。
更隐蔽的是,宽捕获可能意外覆盖本该暴露的 bug:某处本该抛 ValueError 却因上游 except Exception: 被吃掉,导致错误数据流入下游计算。
- 每个
except块应只对应一种可预期、可恢复的故障模式 - 用
isinstance(e, ...)在通用异常处理器内做细粒度分支,比堆砌元组更清晰 - 对不确定是否该捕获的异常,先加
logging.warning+raise,观察线上行为再决定是否降级处理
异步代码中 except 的作用域陷阱
在 async def 函数里,except 只捕获当前协程内同步抛出的异常。如果异常来自 await 的任务(比如 asyncio.create_task() 启动的后台任务),它根本不会触发外层 except —— 而是变成未处理的 Task exception was never retrieved 警告,最终可能被忽略。
常见错误:用 try/except 包裹 await asyncio.gather(...),却没意识到其中某个子任务崩溃后,整个 gather 会以 ExceptionGroup(Python 3.11+)或单个异常(旧版)形式抛出,而非分散到各 except 分支。
- 对并发任务,优先用
asyncio.gather(..., return_exceptions=True)显式收集结果与异常 - 监控后台任务时,务必为
create_task()添加done_callback或定期检查task.exception() - Python 3.11+ 应主动适配
except* ExcType:处理ExceptionGroup,而不是依赖旧式扁平化逻辑
实际项目里最常被忽略的,是异常捕获与资源生命周期的耦合——比如在 finally 中释放句柄,却因宽泛 except 导致提前退出,使 finally 根本不执行。










