知识点思维导图
29 个知识节点
参考资料
Python(09) - 异常处理与上下文管理
读完后,你应能完成以下任务:
- 绘制“Python(09) - 异常处理与上下文管理 / 先给锚点:try/except/finally 和 JS 几乎一一对应”的关键对象与数据流,解释“最大的体感差异:JS 一个 catch 啥都接,Python 习惯按异常类型分开接——这点更像你在 Java 里见过的 catch (XxxException e)。”,并用源码位置、日志或 Trace 标注证据。
- 为“Python(09) - 异常处理与上下文管理 / 边界一:except 要声明类型,且可以接多种”设计正常与异常输入,验证“JS 里 catch (e) 是"一网打尽",再在里面 if (e instanceof XXX) 分流。”,输出首个偏差位置与回归测试结果。
- 实现“Python(09) - 异常处理与上下文管理 / 边界二:多了一个 JS 没有的 else 子句”的最小代码或配置,检验“记忆口诀:try 放"可能炸的",else 放"没炸之后才做的",finally 放"炸不炸都要做的"(通常是清理)。”,输出命令、结果与 Diff,并说明不适用边界。
你在 JS 里写过
try/catch/finally,Python 的异常处理八成长得一样,迁移成本很低。但有几个"看着像、其实多了点东西"的地方:catch要写成except还得声明类型、多了一个 JS 没有的else子句、throw改叫raise。另外本篇还要讲一个前端没有直接对应物的语法——with(上下文管理器),它专门解决"资源用完一定要关掉"这件事,比你手写finally优雅得多。
一、先给锚点:try/except/finally 和 JS 几乎一一对应
你在前端写过的 try/catch/finally,Python 里有等价物,结构基本平移:
// JS / TS
try {
const result = 10 / 0 // JS 里除零不报错,得 Infinity;这里只是占位
JSON.parse("非法 json") // 真正会抛错的地方
} catch (error) { // catch 不用声明类型,啥都接
console.error(error)
} finally {
console.log("无论成败都执行")
}
先用对照表建立直觉:
| JS / TS | Python | 说明 |
|---|---|---|
try { } |
try: |
一致 |
catch (e) { } |
except SomeError as e: |
Python 要声明异常类型 |
finally { } |
finally: |
一致 |
throw new Error(...) |
raise SomeError(...) |
关键字不同 |
| (无) | else: |
Python 独有,见第三节 |
e instanceof TypeError |
except TypeError |
类型匹配 Python 直接写在 except 上 |
最大的体感差异:JS 一个 catch 啥都接,Python 习惯按异常类型分开接——这点更像你在 Java 里见过的 catch (XxxException e)。
二、边界一:except 要声明类型,且可以接多种
JS 里 catch (e) 是"一网打尽",再在里面 if (e instanceof XXX) 分流。Python 把这个分流前置到了 except 上,可以写多个 except 分支,从上往下匹配,命中第一个就停:
// JS 对比:只能在一个 catch 里手动 if 分流
function parseAge(text) {
try {
const age = Number(text)
if (Number.isNaN(age)) throw new TypeError("不是数字")
return Math.floor(100 / age)
} catch (e) {
if (e instanceof TypeError) console.log("请输入数字")
else console.log("其他错误")
}
}
⚠️ 顺序坑:
except从上往下匹配,子类异常要写在父类前面。如果把except Exception(万能父类)写在最上面,下面所有具体的except都永远轮不到。这和 JS 里if/else if的顺序是一个道理。
三、边界二:多了一个 JS 没有的 else 子句
Python 的 try 可以跟一个 else,它在 try 块没抛任何异常时才执行。作用是把"正常后续逻辑"和"可能出错的逻辑"分开,让 try 块尽量只包住真正会抛错的那一两行:
记忆口诀:try 放"可能炸的",else 放"没炸之后才做的",finally 放"炸不炸都要做的"(通常是清理)。JS 没有 else,等价写法是在 try 末尾继续写,但那样不好区分"哪行可能抛异常"。
四、raise:抛异常(对应 JS 的 throw)
// JS
throw new Error("网点 id 不能为空")
几乎一样,只是关键字从 throw 变成 raise,且 Python 习惯用语义化的内置异常类型(ValueError / TypeError / KeyError…)而不是笼统的 Error。
4.1 异常链:raise ... from
捕获一个异常后想重新抛一个更贴业务的异常,用 from 保留原始原因,排查时能看到完整因果链:
五、自定义异常:继承内置异常即可
和 Java 的 BusinessException extends RuntimeException 思路一致,Python 自定义异常只要继承 Exception(或其子类):
// JS 对比:继承 Error,概念完全一样
class BusinessError extends Error {
constructor(message, code = 1) {
super(message)
this.code = code
}
}
和你在 Java 笔记里见过的套路相同:Service 层只管
raise,由一个统一的地方(比如 Web 框架的全局异常处理器)捕获BusinessError,转成{ code, msg }返回前端。后续学 FastAPI 时会用到(详见后面的框架篇)。
5.1 常见内置异常对照
| Python 异常 | 何时出现 | JS 类比 |
|---|---|---|
ValueError |
类型对但值不合法,如 int("abc") |
Number("abc") → NaN |
TypeError |
类型不对,如对 None 调方法 | Cannot read property of undefined |
KeyError |
字典里取不存在的键 d["x"] |
访问对象不存在的属性返回 undefined |
IndexError |
列表越界 arr[99] |
JS 越界返回 undefined(不报错) |
FileNotFoundError |
打开不存在的文件 | Node 的 ENOENT |
注意一个差异:JS 里访问越界数组或不存在的对象属性会静默返回 undefined,Python 会直接抛 IndexError / KeyError——这点对前端是个高频惊吓,养成判断或用 dict.get(key, 默认值) 的习惯。
六、with 语句:本篇的重点,前端没有直接对应物
很多操作"用完必须收尾":文件用完要 close、数据库连接用完要释放、锁用完要解锁。如果手写,你得用 finally 保证收尾一定执行:
Python 提供了 with 语句,把"获取资源 + 保证释放"打包成一行,离开 with 代码块时自动收尾,哪怕中途抛异常也照样收尾:
类比前端帮你定位它的位置:
| 场景 | JS / 前端做法 | Python |
|---|---|---|
| 用完自动清理 | try { } finally { cleanup() } |
with 自动调用清理 |
| TS 5.2 新语法 | using f = openFile()(块结束自动 dispose) |
with 思路完全一致 |
| React 副作用清理 | useEffect 里 return 一个 cleanup 函数 |
概念相近:声明"怎么收尾",由运行时负责调 |
边界提醒:
with不是 try/catch 的替代品。它只负责"保证收尾"(相当于 finally),不负责吞掉异常。with 块里抛的异常该往上抛还是会往上抛,你想处理还得套 try/except。
可以同时管理多个资源,逗号分隔:
七、自定义上下文管理器:with 背后的机制
with 能作用于一个对象,是因为该对象实现了两个魔术方法:__enter__(进入时调,返回值给 as 后面的变量)和 __exit__(离开时调,负责收尾)。
更省事的写法是用标准库 contextlib,把上面一坨简化成一个带 yield 的函数(yield 你在生成器那篇见过):
记忆:yield 那一行就是"分界线"——上半段是进入时跑的,下半段(放进 finally)是离开时跑的。
八、易踩坑清单(前端新手高频)
- 坑 2:
except顺序写反。except Exception写在具体异常前面,会让后面的分支永远命中不到(同第二节)。 - 坑 3:忘了用
with,文件/连接没关,量大时会耗尽文件句柄。能用with就别手写open+close。 - 坑 4:在
finally里return。finally的 return 会覆盖前面的返回值、甚至吞掉正在往上抛的异常,制造诡异 bug,别在 finally 里 return。 - 坑 5:拿索引/键当 JS 用。
arr[99]、d["不存在的键"]在 Python 会抛异常而非返回 undefined,记得用dict.get(k, 默认值)或先判断。
九、总结
- 边界一:except 要声明类型,且可以接多种:JS 里 catch (e) 是"一网打尽",再在里面 if (e instanceof XXX) 分流。
- 边界二:多了一个 JS 没有的 else 子句:记忆口诀:try 放"可能炸的",else 放"没炸之后才做的",finally 放"炸不炸都要做的"(通常是清理)。
- raise:抛异常(对应 JS 的 throw):几乎一样,只是关键字从 throw 变成 raise,且 Python 习惯用语义化的内置异常类型(ValueError / TypeError / KeyError…)而不是笼统的 Error。
- 自定义异常:继承内置异常即可:注意一个差异:JS 里访问越界数组或不存在的对象属性会静默返回 undefined,Python 会直接抛 IndexError / KeyError——这点对前端是个高频惊吓,养成判断或用 dict.get(key, 默认值) 的习惯。
- with 语句:本篇的重点,前端没有直接对应物:很多操作"用完必须收尾":文件用完要 close、数据库连接用完要释放、锁用完要解锁。
- 自定义上下文管理器:with 背后的机制:with 能作用于一个对象,是因为该对象实现了两个魔术方法:enter(进入时调,返回值给 as 后面的变量)和 exit(离开时调,负责收尾)。
学完自测
选择所有正确答案;提交后逐项核对判断依据。