代码语言

知识点思维导图

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:在 finallyreturnfinally 的 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(离开时调,负责收尾)。

学完自测

选择所有正确答案;提交后逐项核对判断依据。

1在“异常处理与上下文管理”中,需要同时满足“先给锚点:try/except/finally 和 JS 几乎一一对应”与“边界一:except 要声明类型,且可以接多种”。给定正文约束“你在前端写过的 try/catch/finally,Python 里有等价物,结构基本平移。”,哪些判断保持了原有处理机制?多选
2“异常处理与上下文管理”出现偏差:“在“异常处理与上下文管理 / 边界二:多了一个 JS 没有的 else 子句”中,即使不满足“Python 的 try 可以跟一个 else,它在 try 块没抛任何异常时才执行”,结果与副作用仍会保持不变。”已成为实际行为。围绕“边界二:多了一个 JS 没有的 else 子句”与“raise:抛异常(对应 JS 的 throw)”,哪些判断能定位被改变的职责或边界?多选
3评审“异常处理与上下文管理”方案时,验收条件包含“捕获一个异常后想重新抛一个更贴业务的异常,用 from 保留原始原因,排查时能看到完整因果链。”。关于“异常链:raise ... from”与“自定义异常:继承内置异常即可”的哪些决策符合正文机制?多选