理解和会写 try/except 只是第一步,写出健壮、可维护的异常处理代码才是目的。这一节总结几条经过大量生产验证的实践原则,直接可用。
原则一:捕获具体的异常,避免裸 except
错误写法:
try:
risky_operation()
except:
print("出错了")
裸 except 会捕获所有异常,包括 KeyboardInterrupt(Ctrl+C)和 SystemExit,导致程序无法被正常中断。
正确写法:
try:
risky_operation()
except ValueError as e:
logger.error(f"数据格式错误:{e}")
except IOError as e:
logger.error(f"文件读写失败:{e}")
- 只捕获你预期可能发生、并且知道如何处理的那几类异常。
- 让未知异常继续往上抛出,暴露问题,避免隐藏 bug。
原则二:不要吞掉异常,至少要记录
错误写法:
try:
do_something()
except Exception:
pass # 静默忽略,问题消失了
吞掉异常是生产事故的常见来源——调用者根本不知道发生了什么,查问题无从下手。
最低要求:
import logging
try:
do_something()
except Exception as e:
logging.exception("处理时发生未预期错误") # 会记录完整堆栈
# 如果无法恢复,可以选择重新 raise
raise
即使当前无法处理,也留下日志证据,方便事后排查。
原则三:finally 用于清理资源,不是用于业务逻辑
finally 块中的代码无论是否发生异常都会执行,最适合做资源释放操作。
file = None
try:
file = open("data.txt", "r")
process(file)
except IOError:
logger.error("文件处理失败")
finally:
if file:
file.close()
更推荐的做法是用上下文管理器(with 语句),它已经内置了资源清理逻辑:
with open("data.txt", "r") as file:
process(file)
# 出了 with 块,文件自动关闭,即使发生异常也一样
对于自定义资源,也建议实现 enter/exit,让使用者可以用 with 统一管理生命周期。
原则四:不要用异常控制正常流程
异常是为处理意外、非正常情况而设计的,用它来做分支判断会导致代码难读且性能较差。
错误示例:
# 用异常判断字典里有没有 key
try:
value = my_dict["key"]
except KeyError:
value = default
正确做法:
value = my_dict.get("key", default)
异常仅用于真正意外的场景(如网络突然断开、文件无权限),普通条件判断留给 if / else。
原则五:异常链与原因传递
在捕获异常后重新抛出新异常时,要保留原始异常信息,避免排查时丢失根因。
错误写法:
try:
open("config.json")
except FileNotFoundError:
raise RuntimeError("配置文件不可用") # 原始异常信息丢失
正确写法(使用 raise ... from ...):
try:
open("config.json")
except FileNotFoundError as e:
raise RuntimeError("配置文件不可用") from e
这样异常堆栈会清晰地显示:“配置文件不可用”是由 FileNotFoundError 引起的,便于定位根因。
原则六:合理使用 else 子句
try...except 还可以带一个 else 块,其中代码仅当 try 块没有任何异常时才会执行。适合放那些“依赖 try 块成功执行”的逻辑。
try:
conn = create_connection()
except ConnectionError:
logger.error("无法建立连接")
else:
# 连接建立成功才执行
execute_query(conn)
这样可以让代码意图更清晰:连接成功与数据库操作紧密相关,但不会被错误地放入 except 分支。
原则七:全局异常处理兜底
在程序的顶层(如 Web 框架的全局中间件、后台任务的主函数)加一个兜底的异常捕获,防止未处理异常导致进程崩溃,并输出完整日志。
def main():
try:
run_app()
except Exception:
logging.exception("应用发生未捕获异常")
# 可以在此做告警、发送通知等
注意:这里捕获后通常需要优雅退出或重启,而不是静默忽略。
总结:记住这四条就够了
日常编码中,只要记住:
- 具体捕获,别用裸
except。 - 不要吞异常,起码记日志。
- 清理资源用
finally或with。 - 异常仅用于异常情况,别当流程控制用。
按这几条写出来的异常处理,既不会无故崩溃,也不会让问题藏到雷区,可以有效减少线上事故的排查时间。