-断言和异常 Lecture 8 - Assertions and Except

官方信息技术老师·24 页·深入(追求细节与边界)·0 次浏览·2 天前
断言机制异常处理适用边界设计意图

断言和异常

看完你能讲清断言与异常的设计意图、各自适用边界与常见误用

按 空格/→ 演示下一步

1 / 24 页

全部页面点击任意一页,跳回舞台从这页播放

断言机制异常处理适用边界设计意图

断言和异常

看完你能讲清断言与异常的设计意图、各自适用边界与常见误用

1第 1 页 · 断言和异常

什么是异常

用生活中的'意外'类比异常概念

什么是异常
用生活中的'意外'类比异常概念
2第 2 页 · 什么是异常

异常与普通错误的区别

上一页我们认识了异常——程序运行时突然冒出来打断流程的信号。但写代码时还有一类问题叫'错误',比如逻辑写反、算法写错,它和异常待遇完全不同。

发生阶段
错误在开发/编译期就能暴露,异常只在运行时才爆发
能否预测
错误源于代码逻辑缺陷,写对即可根除;异常源于外部条件,理论上无法完全避免
处理方式
错误必须改源码纠正,异常可用 try-except 捕获后让程序继续跑
归责对象
错误是程序员的责任,必须改代码;异常是运行环境与外部世界的责任
坐飞机出差对应 →程序运行

航线画错是错误(规划缺陷),飞行遇气流是异常(运行时外部干扰)

3第 3 页 · 异常与普通错误的区别

Python异常继承体系

从顶部 BaseException 往下读,子类继承父类。Exception 一支覆盖大部分业务异常。

图解渲染中…
a1对应 BaseException,捕获它会抓住一切b2Ctrl+C 触发,不归 Exception 管b3sys.exit 抛出,让 except Exception 拦不到c6对应 OSError,d4 是它的常见实例
4第 4 页 · Python异常继承体系

try-except基本结构

上一页我们看到,Python 把所有异常组织成了一张继承表。光认得它们还不够——代码真的跑起来,碰到异常怎么「接住」再继续?这就要用 try-except 这个最小骨架。

try 块
装入可能出错的代码段,从上到下顺序执行
except 子句
声明要捕获的异常类型,下面写处理逻辑
匹配机制
异常抛出时按顺序找第一个类型匹配的 except 接手
最小骨架
至少要 try+except 或 try+finally,二者必有其一
快递签收验货对应 →try-except

开箱过程对应 try 块;货损即异常抛出;拒签或退货就是 except 接手

5第 5 页 · try-except基本结构

except的多种写法

python

演示 except 的三种写法——精确捕获、元组合并、Exception 兜底

代码高亮加载中…

三种 except 对应不同粒度:精确锁定单类、元组合并同类处理、Exception 兜住几乎所有应用层异常。但 Exception 不抓 SystemExit/KeyboardInterrupt。

6第 6 页 · except的多种写法

else子句的作用

python

通过两次调用对比,看 else 在异常与正常情况下的行为差异

代码高亮加载中…

else(L6)仅在 try 未抛异常时执行。对比 L15 正常调用与 L19 异常调用:前者打印"计算成功",后者跳过 else;两者的 finally(L11)都会执行。

7第 7 页 · else子句的作用

finally子句的作用

python

三段调用演示 finally 在正常、被捕获、逃逸三种路径下都会执行——这是它存在的全部理由。

代码高亮加载中…

finally是try-except的兜底,与else互补:else只在没异常时跑,finally无论是否异常都跑——正常、捕获、逃逸三条路径都会执行,适合放资源清理代码。

8第 8 页 · finally子句的作用

异常处理执行流程

从 try 出发,按是否异常走不同分支,但 finally 始终会执行。

图解渲染中…
b1菱形=判断节点,看 try 块是否抛出了异常e1判断是否有 except 能接住这个异常d1无论走哪条分支,finally 都会执行f1无 except 匹配时异常向调用栈上层抛出
9第 9 页 · 异常处理执行流程

异常的传播机制

我们已经学过 try-except 能把异常「接住」。但假设代码里根本没写 try,或者 except 没匹配上——异常不会凭空消失,它会继续往上跑。

抛出即中断
异常一旦抛出,当前函数立即停止,后续代码不再执行
沿调用栈向上传递
异常沿着调用链一级一级向上冒泡
逐层查找 except
每一层函数栈都检查是否有匹配的 except 处理器
无人处理则崩溃
传到 main 都没人接,程序终止并打印 traceback
大楼着火烟往上飘对应 →异常沿调用栈传播

每一层都没人灭火,烟就一路冒到顶楼

10第 10 页 · 异常的传播机制

raise语句的使用

python

用三段函数分别演示 raise 的三种核心写法

代码高亮加载中…

L6 构造并抛出新异常实例;L15 用 from 串联原因链;L22 裸 raise 把当前异常原样再抛一次

11第 11 页 · raise语句的使用

异常链与上下文

python

对比 raise、raise from、raise from None 三种写法,看 __cause__/__context__ 的差异。

代码高亮加载中…

__cause__ 是 from 右侧的对象;__context__ 是 except 块里触发新异常时自动记录的上一异常;from None 只压住 traceback 展示,__context__ 仍存在。

12第 12 页 · 异常链与上下文

LBYL vs EAFP风格

两种防御式编程哲学:先看路 vs 先迈步。Python 推荐 EAFP,但适用场景不同。

LBYL:三思后行
  • 先判断条件,再决定是否行动
  • 成功路径也需付出检查代价
  • 检查与执行间存在 TOCTOU 竞态窗口
EAFP:先斩后奏
  • 直接尝试执行,失败再捕获异常
  • 正常路径零额外开销
  • 原子操作,从根本上避开竞态
Python 中默认选 EAFP:成功路径更快、无竞态;仅当操作不可逆或需提前提示用户时才退守 LBYL。
13第 13 页 · LBYL vs EAFP风格

异常用于控制流的设计

EAFP 鼓励「先做,遇错再说」,但如果把「找对象、查字典」这种正常分支也包进 try 里,就走偏了。什么时候异常适合做控制流?这一页划清边界。

异常的反模式信号
用 except 走「正常分支」、try 块里堆遍日常情况——已经是滥用
性能成本
异常抛出要展开调用栈,比 if 判断慢数十到上百倍
可读性代价
try 里塞满正常情况,主流程被淹没,读代码看不出主线
真正的适用场景
文件不存在、网络中断、解析失败——不可控的外部依赖
急诊室对应 →异常机制

急诊为急症设计,感冒去急诊既浪费资源,又耽误真正急症

14第 14 页 · 异常用于控制流的设计

StopIteration与迭代器

python

for 循环的内部秘密:它其实就是 while + try/except StopIteration 的语法糖

代码高亮加载中…

StopIteration 不是错误,而是迭代器说「我空了」的信号;for 循环本质上就是 while 循环里调用 next(),再用 except 把这个信号转成正常退出。

15第 15 页 · StopIteration与迭代器

断言是什么

assert用于在开发时检查程序员的假设

断言是什么
assert用于在开发时检查程序员的假设
16第 16 页 · 断言是什么

assert的语法与用法

python

看 Python assert 的两种语法形式,以及断言失败时的输出差异。

代码高亮加载中…

L3 是 assert 的最简形式——只有条件;L8 用逗号把字符串追加为失败消息;L17 触发失败时,该消息会跟在 AssertionError 后面输出,便于排查。

17第 17 页 · assert的语法与用法

断言vs异常的区别

排序函数已经写好,但传入不存在的文件时,程序到底该崩溃,还是该交给调用者处理?前页的 assert 适合拦住“不可能发生”的内部错误;异常则要处理“确实可能发生”的运行错误。

断言:内部自检
检查程序不应违反的假设,失败即抛 AssertionError,服务调试而非用户流程
异常:运行故障
通常表示可预见的失败;可捕获、恢复、转换或继续传播,由调用方决定策略
触发与匹配
assert 先求值条件;raise 创建并抛出异常对象,except 再按类型捕获
不可替代边界
断言可能被 -O 关闭,不能承担输入校验、权限检查或资源管理
实验室仪器自检对应 →断言与异常

自检发现内部不可能值就停机;生产故障则由值班员处理、记录或继续上报

18第 18 页 · 断言vs异常的区别

断言的边界情况

python

对比有无 -O 选项时同一段代码的输出,看断言被剥离的隐患

代码高亮加载中…

L4 提示存在 -O 选项;L8 的 assert 在该模式下被剥离为空操作;L10 的扣款失去校验保护,超额照样执行。

19第 19 页 · 断言的边界情况

常见异常类型一览

python

七行代码,七种典型异常,看清各自在什么场景下被抛出。

代码高亮加载中…

每种异常都精确指向一种错误场景:类型错了抛 TypeError,值不合法抛 ValueError,越界了抛 IndexError——异常类型本身就是第一份诊断信息。

20第 20 页 · 常见异常类型一览

最佳实践与反模式

python

三段对比:裸 except、except Exception、精确捕获,看错误处理怎么从吞 Bug 变成能定位 Bug。

代码高亮加载中…

L5 的 except: 实际捕获 BaseException,Ctrl+C 都被吞;L13 的 Exception 把 KeyError 也吃进去,调试找不到根因;L20 只接能处理的两种,其余让异常继续冒泡。

21第 21 页 · 最佳实践与反模式

自测练习

点击作答

下面关于 assert 语句的说法,哪个是正确的?

22第 22 页 · 自测练习

知识要点回顾

  • 异常处理:精准捕获,不要越界
  • EAFP 是 Pythonic 的默认姿态
  • 断言守内部不变式,异常管运行时
  • finally 只做资源清理,不放业务
  • 异常要传播带链,严禁裸 except
延伸主题:自定义异常体系设计with 与 contextlib 资源管理traceback 调试技巧
23第 23 页 · 知识要点回顾

深入思考

先独立思考,再对照参考答案;问题没有唯一解。

1为什么 Python 选择用异常而非错误码来处理错误?这体现了什么设计哲学?

参考答案异常把「正常路径」与「错误路径」分离,避免调用方每步检查返回值——EAFP 风格的核心。错误码会让业务逻辑淹没在错误检查中。

2在多层调用的程序中,底层异常应该就地处理还是向上传播?判断依据是什么?

参考答案取决于当前层是否拥有足够的上下文。能恢复或补充信息,就处理并转换异常类型;否则让它向上传播,由最外层统一兜底和记录日志。

3如果用 -O 选项运行程序,断言会被全部禁用,原本依赖 assert 做参数校验的代码会发生什么?

参考答案校验形同虚设,非法输入会继续往后传,可能引发更隐蔽的运行时错误。所以参数校验必须用 raise,绝不能用 assert 替代。

24第 24 页 · 深入思考
-断言和异常 Lecture 8 - Assertions and Except · 知识图解