P04第 1 / 1 课时未开始内容版本 1.0.0 · 核验于 2026-10-26

异常与资源管理

异常与资源管理 · 阅读后完成下方实验与检查题,再用「标记完成」记录进度。

学习目标

这一课解决两个必然发生的问题:文件不存在/内容损坏怎么办,文件读一半崩了谁来关。目标是写出「错误信息有用、资源必定释放」的代码,并且不吞异常。

问题场景

P06 要做的校验工具会遍历几十个 JSON 文件:有的不存在、有的被手改坏了、有的编码不对。裸跑第一版大概长这样:

f = open(path)
data = json.load(f)   # 文件不存在?内容是坏的?全抛异常直接崩

JS 里你写过 try/catch 加 finally,Python 的结构几乎一样,但多一个关键设施:with 语句,它把「必定关闭」从纪律变成语言机制。

核心解释

异常处理的原则按顺序就三条:只捕获能处理的、错误信息要保留上下文、资源交给 with。

import json

def load_records(path: str) -> list:
    try:
        with open(path, encoding="utf-8") as f:
            return json.load(f)
    except FileNotFoundError:
        print(f"跳过:文件不存在 {path}")
        return []
    except json.JSONDecodeError as e:
        raise ValueError(f"课程文件损坏: {path} 第{e.lineno}行") from e

三个要点逐个说清:

  1. except 要写具体类型。except Exception: 或裸 except: 会把拼写错误、逻辑 bug 一起吞掉,你只会看到莫名其妙的「没输出」。只捕获你确实能处理的那一两种。
  2. raise ... from e 保留异常链。转成业务异常时把原始异常挂上去,日志里能看到完整因果链;直接 raise ValueError(...) 会丢掉「第几行」这类原始信息。
  3. with 等价于 JS 的 using/try-finally 组合。块内无论正常返回还是抛异常,f.close() 必定执行。自己写资源管理时用 contextlib.contextmanager 达到同样效果。

文件之外,sqlite3.connect()、socket 同样适用 with。P11 写数据库、P18 看日志时,这套结构会反复出现。

示例拆解

用 P03 的模块结构验证异常路径:

# main.py
from parsing import load_records

if __name__ == "__main__":
    print(load_records("not-exist.json"))       # 跳过:文件不存在 ... → []
    try:
        load_records("broken.json")             # 内容是 {bad json
    except ValueError as e:
        print("捕获:", e)                        # 捕获: 课程文件损坏: broken.json 第1行

预期行为:缺文件返回空列表并提示(可恢复,程序继续跑);坏文件抛出带路径和行号的业务异常(不可静默,调用方必须知道)。这个「可恢复的记下来继续、不可恢复的带上下文抛出去」的分界线,就是判断一段异常处理写没写对的唯一标准。

动手任务

本课实验区有可直接运行的版本(纯标准库):

  1. 给 load_records 补上 PermissionError 分支,处理无权限文件,返回空列表并打印原因。
  2. 用 contextlib.contextmanager 写一个 open_quiet(path) 上下文管理器:进入返回文件对象,退出自动关闭并打印「closed」。
  3. 把某个 except 改成裸 except: pass,制造一个拼写错误感受「异常被吞」的排查难度,然后改回来。

常见错误

  • except: pass 静默吞掉:出错了但程序「看起来正常」,最难的 bug 都这么来的;至少要打印或记日志。
  • 捕获范围过大:except Exception 把 KeyboardInterrupt 之外的全接住,调试信息全没了;写具体异常类型。
  • 在 except 里 return 掩盖问题:本该让上层知道的失败被一个默认值盖掉;先想清楚这个失败谁能处理。
  • 手动 close 不用 with:异常路径上必漏;资源一律 with,自己包装就用 contextmanager。

检查与复盘

自查清单:

  • 代码里找不到裸 except: 和 except: pass
  • 能说清 FileNotFoundError 和 JSONDecodeError 分别该恢复还是该抛
  • 打开过的每个资源都由 with 管理而不是手动 close

参考资料

动手实验
动手实验
预览
点击「运行」在隔离沙箱中预览
检查题共 2 题,提交后立即显示解析
答案由服务端判定;答错的题会自动进入复习计划。
Q1读代码中等

阅读代码(Python 3.11+,独立脚本可直接运行,config.json 存在且内容合法)。输出什么?文件最后被关闭了吗?

def read_config():
    f = open('config.json', encoding='utf-8')
    try:
        return f.read()
    finally:
        f.close()

print(type(read_config()).__name__)
Q2单选简单

解析用户上传的 JSON 配置,可能抛出 json.JSONDecodeError 或 KeyError。下列 except 用法最合理的是?

读完这一课了?

标记完成后,工作台会安排接下来的学习。

Command Palette

Search for a command to run...