文章目录
- Python 异常处理:你写的 try-except 可能比不写更危险
- 导入语
- 1 ~> Python 的异常体系——一张图看清楚
- 2 ~> 最常见的四种错误写法
- 2.1 错误一:裸捕获 `except:`
- 2.2 错误二:捕获范围过大 `except Exception`
- 2.3 错误三:吞掉异常 `except: pass`
- 2.4 错误四:在 finally 里写 return
- 3 ~> try/except/else/finally 完整的四块
- 4 ~> 异常链——一个真实场景
- 5 ~> 自定义异常
- 思考 && 总结
- 结尾
Python 异常处理:你写的 try-except 可能比不写更危险
📖文章简介:这篇文章至少能帮你改掉两个坏习惯:except:裸捕获和except Exception: pass吞异常。初学 try-except 的时候,本能反应是"用 try 包住所有可能报错的代码,except 后面写 pass 就行"。但这两招在生产环境中会让你的 Bug 隐形。本文从 Python 异常体系结构讲起,拆解 try/except/else/finally 四块完整的语法和各自执行的时机,深入"捕获范围过大"“吃掉异常”“异常链”"自定义异常"四个核心场景,文末附真实案例——一条因为裸 except 吞掉了 PermissionError 导致数据不一致的生产事故。
🎬 个人主页:源码骑士
❄专栏传送门:《Android开发基础》《python基础课程》
⭐️热衷从源码视角拆解技术底层原理,将复杂架构讲得通俗易懂
🎬 源码骑士的简介:
5年Android Framework系统开发经验,曾主导多项系统级性能优化专项
技术栈覆盖Android系统全链路(Binder/Handler/AMS/WMS/启动流程)及Java后端全家桶(Spring + MyBatis + Redis + Oracle)
累计产出原创技术文章100+篇,文章以源码拆解为特色,被读者评价为"看一篇胜过啃一周文档"
导入语
如果你接触 Python 的第一周就学了 try-except,我敢打赌你一定写过这种代码:
try:do_something()except:pass是的——“不知道会报什么错,先包起来再说”。新人阶段这么写很正常。但如果到了生产环境你还这么写,问题就大了。我曾经处理过一个线上事故——订单导出功能偶尔丢数据。排查了好久发现根源就是一行except: pass把 PermissionError(文件无法写入)给吞了。数据在内存里算好了,写到磁盘的时候因为权限问题挂了,但因为没有日志,上游系统一直以为写入成功。
不用 try-except 会崩,乱用 try-except 会埋炸弹。这篇文章帮你搞清楚怎么正确地用。
1 ~> Python 的异常体系——一张图看清楚
Python 所有异常的根基是BaseException:
BaseException ├─ Exception ← 常见的异常,咱们日常要处理的 │ ├─ ValueError │ ├─ TypeError │ ├─ KeyError │ ├─ IndexError │ ├─ FileNotFoundError │ ├─ ConnectionError │ └─...(你自定义的异常也挂这儿) ├─ KeyboardInterrupt ← Ctrl+C 触发的,不是 Exception 的子类 ├─ SystemExit ← sys.exit()触发的 └─ GeneratorExit ← 生成器关闭时触发的当你写except:(裸捕获)时,它等价于except BaseException:——连 KeyboardInterrupt 和 SystemExit 都会捕获。这意味着用户按 Ctrl+C 想退出程序?被你拦住了。sys.exit()想优雅退出?也被你拦住了。
2 ~> 最常见的四种错误写法
2.1 错误一:裸捕获except:
# ❌ 极度危险try:result=10/0except:# 连 KeyboardInterrupt 都会捕获pass危害:按 Ctrl+C 无效,程序卡死只能强制结束进程。SystemExit也被拦截。
修复:
try:result=10/0exceptExceptionase:# 至少限定 Exceptionprint(f"出错:{e}")2.2 错误二:捕获范围过大except Exception
# ⚠️ 不太好try:data=json.loads(user_input)result=process(data)save_to_db(result)exceptExceptionase:print(f"处理失败:{e}")# 但是不知道是哪一步出了问题危害:三行代码各自抛不同的异常——json.JSONDecodeError、ValueError、DatabaseError——全被同一个 except 吞了。你既不知道哪一步出的错,也无法对不同错误采取不同的恢复策略。
# ✅ 精准捕获try:data=json.loads(user_input)exceptjson.JSONDecodeError:return{"error":"JSON 格式有误"}2.3 错误三:吞掉异常except: pass
# ❌ 生产事故之源try:write_to_file(data)exceptOSError:pass# 文件写失败了,程序一律当成功继续走这是比重试失败更严重的问题——数据丢失了你都不知道。至少记个日志:
importloggingtry:write_to_file(data)exceptOSErrorase:logging.exception("文件写入失败")raise# re-raise,让上层知道这里出事了2.4 错误四:在 finally 里写 return
defbad_example():try:return1finally:return2# finally 的 return 会覆盖 try 的 return!print(bad_example())# 输出:2 ← try 的 return 1 被吃了finally块在 try 的 return 执行之后、函数真正返回之前执行。如果finally里也有return,会覆盖掉 try 的返回值。
3 ~> try/except/else/finally 完整的四块
defrobust_read_file(filename):file=Nonetry:file=open(filename,"r",encoding="utf-8")exceptFileNotFoundError:print(f"文件{filename}不存在")returnNoneexceptPermissionError:print(f"无权限读取{filename}")returnNoneelse:# else:try 块没有异常时才执行(有异常就跳过了)content=file.read()returncontentfinally:# finally:无论是否异常,一定会执行# 这里最适合做清理工作:关文件、释放锁、断开连接iffile:file.close()| 块 | 什么时候执行 | 最适合做什么 |
|---|---|---|
try | 正常逻辑 | 可能出错的代码放这里 |
except | try 中的代码抛出匹配的异常时 | 根据不同类型的异常做不同处理 |
else | try 中无异常时 | 把"依赖 try 成功才运行的代码"放这里(和 try 区分开,更清晰) |
finally | 无论如何都会执行(即使有 return) | 清理资源:关闭文件、释放锁、断开数据库连接 |
4 ~> 异常链——一个真实场景
当你在 except 里再次抛出异常时,使用raise ... from ...来保留原始上下文:
defload_config(path):try:withopen(path)asf:returnjson.load(f)exceptFileNotFoundErrorase:raiseRuntimeError(f"配置文件{path}读取失败")fromefrom e的作用:异常追踪会同时显示RuntimeError和原始FileNotFoundError,而不是把原始异常吃掉。这在排查复杂系统的线上问题时至关重要——你不仅要看到"配置文件加载失败",还要看到"为什么失败(文件不存在)"。
5 ~> 自定义异常
classPaymentError(Exception):"""支付相关的异常基类"""classInsufficientBalanceError(PaymentError):"""余额不足"""def__init__(self,required,available):self.required=required self.available=availablesuper().__init__(f"需要{required}元,余额{available}元")defpay(amount,balance):ifbalance<amount:raiseInsufficientBalanceError(amount,balance)returnbalance-amounttry:pay(100,30)exceptInsufficientBalanceErrorase:print(f"支付失败:{e}")print(f"差额:{e.required-e.available}元")思考 && 总结
三条铁律:
- 永远不要用裸
except:。至少写except Exception as e。裸捕获会拦截 Ctrl+C 和 sys.exit()。 - 捕获要精准。不同类型的异常应该有各自的处理方式。别把所有代码塞一个 try 里,精准异常才能制定精准的恢复策略。
- 吞异常至少记日志。
except: pass是生产环境下仅次于空指针的危险操作。真的不需要处理的异常也要用logging.exception()记下来。
结尾
各位小伙伴,异常处理到这里就讲完了。感谢阅读!
源码骑士 — Python 全栈 & 系统架构
👀关注:跟博主一起从源码视角深耕底层原理,见证每一次成长
❤️点赞:让优质内容被更多人看见,让知识传递更有力量
⭐收藏:把核心知识点存好,在需要时随时查、随时用
💬评论:分享你的经验或疑问,评论区一起交流避坑
🔄一键四连:不要忘记给博主"一键四连"哦!今日源码拆解达成!
🗡️寄语:技术之路,同行的人会让前路更有方向
结语:异常处理少即是多——精准捕获比大包大揽更安全。下篇讲 import 的机制和那些找不到模块的坑。