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

文件、JSON 与实用脚本

文件、JSON 与实用脚本 · 阅读后完成下方实验与检查题,再用「标记完成」记录进度。

学习目标

这一课把前面四课攒的零件拼成一个真工具:扫描本站 content/courses 下的课程元数据,校验 JSON 合法性,坏文件不中断,最后输出一份能定位到具体文件和原因的报告。

问题场景

本站 180 门课的 course.json 全靠人维护,手滑写坏一个 JSON、漏一个必填字段,npm run db:seed 就会在某个深夜导入失败。你需要一个预检脚本:python check_courses.py 一跑,哪个文件、哪一行、什么问题,一屏看全。

这就是「实用脚本」的标准形态:读文件、解析、校验、报告——每一步都用到 P01 的环境、P03 的模块拆分、P04 的异常处理。

核心解释

三个基础件先钉牢:

  1. pathlib 管路径。Path("content/courses") 拼接用 / 运算符(p / "python" / "p07-fastapi-basics"),跨平台不用管斜杠;glob("*/course.json") 一次列出所有目标文件。
  2. 编码必须显式。open(p, encoding="utf-8") 写全——Windows 默认编码是 GBK,中文 JSON 不写 encoding 就是乱码或炸 UnicodeDecodeError。写回用 json.dump(data, f, ensure_ascii=False, indent=2),中文保持原样而不是 \uXXXX。
  3. 坏文件是常态不是异常。校验工具的生命线是「单个文件失败不影响整体」,P04 的 try/except 在这里第一次派上真正用场。

核心循环长这样:

# check_courses.py
import json
import sys
from pathlib import Path

REQUIRED = ["code", "slug", "title", "track"]

def check_file(path: Path) -> str | None:
    """返回 None 表示通过,否则返回问题描述。"""
    try:
        data = json.loads(path.read_text(encoding="utf-8"))
    except json.JSONDecodeError as e:
        return f"JSON 解析失败(第{e.lineno}行)"
    missing = [k for k in REQUIRED if k not in data]
    if missing:
        return f"缺少必填字段: {', '.join(missing)}"
    return None

def main() -> int:
    problems = []
    for path in sorted(Path("content/courses").glob("*/*/course.json")):
        if err := check_file(path):
            problems.append(f"{path.as_posix()}: {err}")
    for line in problems:
        print("FAIL", line)
    print(f"共 {len(problems)} 个问题")
    return 1 if problems else 0          # 退出码:有错为 1

if __name__ == "__main__":
    sys.exit(main())

示例拆解

制造坏文件再运行验证:

python check_courses.py
# FAIL content/courses/python/p08-request-response-validation/course.json: JSON 解析失败(第3行)
# 共 1 个问题
echo $?
# 1 —— 退出码非 0,可以被 npm script / CI 判定失败

三个设计点值得复述一遍:报告带文件路径 + 行号,拿到就能改,不是笼统一句「有错」;坏文件收集后继续,一次跑出全部问题而不是修一个跑一遍;退出码区分通过与否,脚本才能被别的工具链调用。把目录参数化成 sys.argv,这个脚本就能复用到任何 JSON 目录上。

动手任务

本课实验区有可直接运行的版本(纯标准库,针对临时样例目录校验):

  1. 给 check_file 补充校验:prereqs 存在时必须是字符串数组,不合法返回对应描述。
  2. 把目录改成 sys.argv[1] 传入,没有参数时打印用法并返回退出码 2。
  3. 让报告额外统计「通过数 / 总数」,末尾输出 checked=10 passed=9 failed=1 一行汇总。

常见错误

  • 不写 encoding 读中文文件:Windows 默认 GBK,轻则乱码重则异常;读写一律显式 encoding="utf-8"。
  • 拼接路径用字符串加号:"dir" + "/" + name 在跨平台时埋雷;用 Path 的 / 运算符。
  • 一个坏文件中断全部:把 json.load 写在循环里不捕获,错一个停一串;逐文件收集错误。
  • JSON 写回变成 \uXXXX:忘了 ensure_ascii=False,中文全被转义,diff 里没法看。

检查与复盘

自查清单:

  • 脚本能对「好文件、坏 JSON、缺字段」三种情况给出各自准确的报告
  • 报告里每条都包含文件路径和可定位的原因
  • 退出码行为正确,且说得出它对调用方的意义

参考资料

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

bug 现场:脚本想把处理结果合并回 data.json,运行报 json.decoder.JSONDecodeError: Expecting value: line 1 column 1。代码与报错位置如下,问题在哪、怎么修?

import json

path = 'data.json'

with open(path, 'w', encoding='utf-8') as f:
    pass  # 意图:先清空旧文件

with open(path, 'r', encoding='utf-8') as f:
    old = json.load(f)   # ← 这里报错
Q2单选简单

用 json.dumps(data, ensure_ascii=False, indent=2) 写配置文件,这两个参数的作用是?

读完这一课了?

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

Command Palette

Search for a command to run...