-断言和异常 Lecture 8 - Assertions and Except
断言和异常
看完你能讲清断言与异常的设计意图、各自适用边界与常见误用
按 空格/→ 演示下一步
全部页面点击任意一页,跳回舞台从这页播放
断言和异常
看完你能讲清断言与异常的设计意图、各自适用边界与常见误用
什么是异常
用生活中的'意外'类比异常概念
异常与普通错误的区别
上一页我们认识了异常——程序运行时突然冒出来打断流程的信号。但写代码时还有一类问题叫'错误',比如逻辑写反、算法写错,它和异常待遇完全不同。
航线画错是错误(规划缺陷),飞行遇气流是异常(运行时外部干扰)
Python异常继承体系
从顶部 BaseException 往下读,子类继承父类。Exception 一支覆盖大部分业务异常。
try-except基本结构
上一页我们看到,Python 把所有异常组织成了一张继承表。光认得它们还不够——代码真的跑起来,碰到异常怎么「接住」再继续?这就要用 try-except 这个最小骨架。
开箱过程对应 try 块;货损即异常抛出;拒签或退货就是 except 接手
except的多种写法
演示 except 的三种写法——精确捕获、元组合并、Exception 兜底
三种 except 对应不同粒度:精确锁定单类、元组合并同类处理、Exception 兜住几乎所有应用层异常。但 Exception 不抓 SystemExit/KeyboardInterrupt。
else子句的作用
通过两次调用对比,看 else 在异常与正常情况下的行为差异
else(L6)仅在 try 未抛异常时执行。对比 L15 正常调用与 L19 异常调用:前者打印"计算成功",后者跳过 else;两者的 finally(L11)都会执行。
finally子句的作用
三段调用演示 finally 在正常、被捕获、逃逸三种路径下都会执行——这是它存在的全部理由。
finally是try-except的兜底,与else互补:else只在没异常时跑,finally无论是否异常都跑——正常、捕获、逃逸三条路径都会执行,适合放资源清理代码。
异常处理执行流程
从 try 出发,按是否异常走不同分支,但 finally 始终会执行。
异常的传播机制
我们已经学过 try-except 能把异常「接住」。但假设代码里根本没写 try,或者 except 没匹配上——异常不会凭空消失,它会继续往上跑。
每一层都没人灭火,烟就一路冒到顶楼
raise语句的使用
用三段函数分别演示 raise 的三种核心写法
L6 构造并抛出新异常实例;L15 用 from 串联原因链;L22 裸 raise 把当前异常原样再抛一次
异常链与上下文
对比 raise、raise from、raise from None 三种写法,看 __cause__/__context__ 的差异。
__cause__ 是 from 右侧的对象;__context__ 是 except 块里触发新异常时自动记录的上一异常;from None 只压住 traceback 展示,__context__ 仍存在。
LBYL vs EAFP风格
两种防御式编程哲学:先看路 vs 先迈步。Python 推荐 EAFP,但适用场景不同。
- 先判断条件,再决定是否行动
- 成功路径也需付出检查代价
- 检查与执行间存在 TOCTOU 竞态窗口
- 直接尝试执行,失败再捕获异常
- 正常路径零额外开销
- 原子操作,从根本上避开竞态
异常用于控制流的设计
EAFP 鼓励「先做,遇错再说」,但如果把「找对象、查字典」这种正常分支也包进 try 里,就走偏了。什么时候异常适合做控制流?这一页划清边界。
急诊为急症设计,感冒去急诊既浪费资源,又耽误真正急症
StopIteration与迭代器
for 循环的内部秘密:它其实就是 while + try/except StopIteration 的语法糖
StopIteration 不是错误,而是迭代器说「我空了」的信号;for 循环本质上就是 while 循环里调用 next(),再用 except 把这个信号转成正常退出。
断言是什么
assert用于在开发时检查程序员的假设
assert的语法与用法
看 Python assert 的两种语法形式,以及断言失败时的输出差异。
L3 是 assert 的最简形式——只有条件;L8 用逗号把字符串追加为失败消息;L17 触发失败时,该消息会跟在 AssertionError 后面输出,便于排查。
断言vs异常的区别
排序函数已经写好,但传入不存在的文件时,程序到底该崩溃,还是该交给调用者处理?前页的 assert 适合拦住“不可能发生”的内部错误;异常则要处理“确实可能发生”的运行错误。
自检发现内部不可能值就停机;生产故障则由值班员处理、记录或继续上报
断言的边界情况
对比有无 -O 选项时同一段代码的输出,看断言被剥离的隐患
L4 提示存在 -O 选项;L8 的 assert 在该模式下被剥离为空操作;L10 的扣款失去校验保护,超额照样执行。
常见异常类型一览
七行代码,七种典型异常,看清各自在什么场景下被抛出。
每种异常都精确指向一种错误场景:类型错了抛 TypeError,值不合法抛 ValueError,越界了抛 IndexError——异常类型本身就是第一份诊断信息。
最佳实践与反模式
三段对比:裸 except、except Exception、精确捕获,看错误处理怎么从吞 Bug 变成能定位 Bug。
L5 的 except: 实际捕获 BaseException,Ctrl+C 都被吞;L13 的 Exception 把 KeyError 也吃进去,调试找不到根因;L20 只接能处理的两种,其余让异常继续冒泡。
自测练习
下面关于 assert 语句的说法,哪个是正确的?
知识要点回顾
- ✓异常处理:精准捕获,不要越界
- ✓EAFP 是 Pythonic 的默认姿态
- ✓断言守内部不变式,异常管运行时
- ✓finally 只做资源清理,不放业务
- ✓异常要传播带链,严禁裸 except
深入思考
先独立思考,再对照参考答案;问题没有唯一解。
参考答案异常把「正常路径」与「错误路径」分离,避免调用方每步检查返回值——EAFP 风格的核心。错误码会让业务逻辑淹没在错误检查中。
参考答案取决于当前层是否拥有足够的上下文。能恢复或补充信息,就处理并转换异常类型;否则让它向上传播,由最外层统一兜底和记录日志。
参考答案校验形同虚设,非法输入会继续往后传,可能引发更隐蔽的运行时错误。所以参数校验必须用 raise,绝不能用 assert 替代。