10 / 90

Day 10: 文件、路径与编码

把脆弱的文件读取变成可预测的程序:把路径锚定到脚本、显式写出编码、用 with 释放资源,并凭证据诊断缺失文件和无效内容。 Retrieval and lower-new-content review are part of the 480-minute ledger.

Self-Directed Learning & Python 480 active minutes (8 hours focused study)
01 120 分钟

路径、编码与安全的文件读取

只有在记录了预测和观察到的证据之后,才允许使用协助。

Prerequisites

完成上一个活动;如遇卡壳,请使用列出的恢复链接。

Learning Objective

在 with 块内用显式 UTF-8 编码读取文本文件并统计非空行数。运行前先预测结果,再解释为什么相对路径取决于当前工作目录。

Instructions

学习来源:在本活动现有的阅读与练习时段内,参考 Automate the Boring Stuff with Python, 3rd edition 的 Chapter 10; Practice Questions; Appendix B answers,用自己的话解释一处与本地任务的联系。保留现有练习与验收检查;若外部资料无法访问,使用课程自写的解释。在全新目录中创建 notes.txt,恰好三行:alpha、beta、gamma。open("notes.txt") 这样的相对路径相对 Path.cwd() 解析,而不是相对脚本所在文件夹。迭代文件对象每次产生一行,每行都保留结尾的 "\n",但最后一行可能例外:如果文件不以换行符结尾,最后一行就没有换行符(写成 "alpha\nbeta\ngamma" 的文件,最后一行是 'gamma')。line.strip() 会去掉该换行符及前后空白,因此由它判断一行是否为空。with 块是预定义的清理动作:即使读取抛出异常也会关闭文件。

Practice & Prediction Log

count = 0
with open("notes.txt", encoding="utf-8") as f:
for line in f:
if line.strip():
count += 1
print(count, f.closed)
运行前先写下它会打印的那一行,并解释两个值各自为什么是这样。如果 notes.txt 不以换行符结尾,结果会变吗?

Independent Mastery Criterion

预测与观察到的输出一致;能说明相对路径相对 Path.cwd() 解析;能展示 with 块结束后文件对象的关闭状态(f.closed)。

Authoritative Source Resources & Provenance

linking Copyright Al Sweigart; edition-specific reuse terms unverified linking only; no reproduction

Automate the Boring Stuff with Python, 3rd edition

作者 / 机构: Al Sweigart | 原始出处链接 ↗

对应章节 / 时间戳: Chapter 10; Practice Questions; Appendix B answers

验证版本与日期: Public edition checked 2026-11-01; moving pages may change

入选理由:

Beginner teaching and answer-keyed practice before checking exact semantics in the Python reference; selected chapters only, not the book's entire automation stack.

来源前置条件:

No prior programming required for Chapter 1; later chapters assume its basic Python concepts.

安全回退方案(离线弹性):

Use the original localized explanations, exercises and checks already present in this activity; no external account or purchase is required for that fallback.

对比候选资源与选型分析 (2)
The Python Tutorial ↗ compared_alternative [PSF License Version 2]

Guided Python syntax but assumes programming familiarity; paired with the beginner text rather than used as its replacement.

MIT OCW 6.0001 ↗ compared_alternative [CC BY-NC-SA 4.0]

Lectures and problem sets teach a broader CS1 course; the book offers shorter text-first entry and an answer appendix.

linking PSF License Version 2

The Python Tutorial: Input and Output

作者 / 机构: Python Software Foundation | 原始出处链接 ↗

对应章节 / 时间戳: Section 7.2 (Reading and Writing Files)

验证版本与日期: Python 3.12 documentation; verified 2026-10-30

入选理由:

Primary reference for open(), explicit encodings, and the with statement as guaranteed cleanup.

来源前置条件:

安全回退方案(离线弹性):

The lesson explains all required behavior locally and can be completed offline; help(open) also works in the interpreter.

对比候选资源与选型分析 (2)
Real Python: Reading and Writing Files ↗ compared_alternative [Proprietary (free and paid tiers)]

Friendly walkthrough, but it is a secondary tutorial with mixed access tiers rather than the normative language reference.

io — Core tools for working with streams ↗ compared_alternative [PSF License Version 2]

Stream-layer reference for advanced cases; the tutorial chapter covers the open()/with patterns the drills require.

02 120 分钟

先诊断缺失文件,再改代码

只有在记录了预测和观察到的证据之后,才允许使用协助。

Prerequisites

完成上一个活动;如遇卡壳,请使用列出的恢复链接。

Learning Objective

复现 open("data/notes.txt") 抛出的 FileNotFoundError,并在改动任何代码之前,用 Path.exists() 和 Path.cwd() 的证据把原因归类为文件不存在或工作目录错误。

Instructions

只在一次性夹具中操作:本活动的脚本会在一个全新的临时目录里创建 project/(包含 reader.py 和 data/notes.txt)以及一个空的同级文件夹 outside/,并在退出时删除它们,因此结果不会取决于你的家目录或真实项目里碰巧有什么。消息 [Errno 2] No such file or directory: 'data/notes.txt' 只说明相对路径无法从当前工作目录解析,无法区分两种原因。先收集证据:比较 Path.cwd() 与脚本所在文件夹,求值 Path("data/notes.txt").exists()(相对 cwd),再令 BASE = Path(__file__).resolve().parent 并求值 (BASE / "data" / "notes.txt").exists()(相对脚本)。如果锚定路径存在而相对路径不存在,说明工作目录错了:改用 open(BASE / "data" / "notes.txt", encoding="utf-8")。如果锚定路径也不存在,则夹具确实缺失,需要创建或恢复;改代码无济于事。

Practice & Prediction Log

从任意目录运行这个脚本。它会创建一次性夹具,然后运行未修改的 reader.py(打开相对路径 data/notes.txt):一次以 project 文件夹为 cwd,一次以 outside/ 为 cwd。运行前,预测两次运行的退出码和打印的每一行。然后根据证据归类原因,并在不重写计数逻辑的前提下写出 reader.py 的最小修复。

import subprocess
import sys
import tempfile
from pathlib import Path

READER = (
"from pathlib import Path\n"
"print('cwd is project:', Path.cwd() == Path(__file__).resolve().parent)\n"
"print('exists:', Path('data/notes.txt').exists())\n"
"with open('data/notes.txt', encoding='utf-8') as f:\n"
" print('lines:', sum(1 for line in f if line.strip()))\n"
)

with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp).resolve()
project = root / "project"
outside = root / "outside"
(project / "data").mkdir(parents=True)
outside.mkdir()
(project / "data" / "notes.txt").write_text("alpha\nbeta\ngamma\n", encoding="utf-8")
(project / "reader.py").write_text(READER, encoding="utf-8")
for label, cwd in [("project", project), ("outside", outside)]:
run = subprocess.run([sys.executable, str(project / "reader.py")],
cwd=cwd, capture_output=True, text=True)
print(f"[{label}] exit={run.returncode}")
print(run.stdout, end="")
if run.stderr:
print(run.stderr.strip().splitlines()[-1])

Independent Mastery Criterion

日志写明两种原因中哪一个成立、给出证明它的 cwd/exists() 证据,并应用最小修复(锚定路径或恢复文件),而不是重写读取程序。

Authoritative Source Resources & Provenance

linking PSF License Version 2

Python Standard Library: Built-in Exceptions

作者 / 机构: Python Software Foundation | 原始出处链接 ↗

对应章节 / 时间戳: Concrete exceptions and Exception hierarchy

验证版本与日期: Python 3.12 documentation; verified 2026-10-30

入选理由:

Normative reference for FileNotFoundError attributes such as filename and errno used in the diagnosis drill.

来源前置条件:

安全回退方案(离线弹性):

The exercise reproduces FileNotFoundError locally, so the evidence exists even without the reference page.

对比候选资源与选型分析 (2)
W3Schools Python Exceptions ↗ compared_rejected_for_day10 [Proprietary]

Beginner-friendly but not normative; its simplified hierarchy omits the OSError attributes the drill inspects.

The Python Tutorial: Errors and Exceptions ↗ compared_alternative [PSF License Version 2]

Friendlier first-pass narrative; the library reference was selected for the normative hierarchy the exercises name directly.

linking PSF License Version 2

Python Standard Library: pathlib

作者 / 机构: Python Software Foundation | 原始出处链接 ↗

对应章节 / 时间戳: Path.exists, Path.cwd, and anchoring paths to __file__

验证版本与日期: Python 3.12 documentation; verified 2026-10-30

入选理由:

Normative reference for exists()/cwd() evidence that separates an absent file from a wrong working directory.

来源前置条件:

安全回退方案(离线弹性):

Path.exists() and Path.cwd() are exercised directly in the local drill, so no online page is required.

对比候选资源与选型分析 (2)
os.path documentation ↗ compared_alternative [PSF License Version 2]

Functionally equivalent for exists() checks, but pathlib keeps the beginner syntax object-oriented and consistent across activities.

shutil — High-level file operations ↗ compared_alternative [PSF License Version 2]

Covers copying and moving whole trees rather than path modeling; pathlib remains the right reference for the filesystem checks these days rehearse.

03 120 分钟

无效内容:带跳过报告的容错解析

只有在记录了预测和观察到的证据之后,才允许使用协助。

Prerequisites

完成上一个活动;如遇卡壳,请使用列出的恢复链接。

Learning Objective

解析一个含有一行畸形数据的分数文件:记录每个被跳过的行号并继续处理。区分 UnicodeDecodeError(用错编码解码字节)与 ValueError(文本有效、数值无效)。

Instructions

行形如 ana,7。int("x9") 抛出 ValueError: invalid literal for int() with base 10: 'x9'。只用 try/except ValueError 包住 int(raw) 调用,把行号追加到 skipped 列表,并且只在解析成功时保存分数。解码问题是另一类失败:字节 b'caf\xe9'(以 Latin-1 保存的 café)不是有效的 UTF-8,所以 b'caf\xe9'.decode("utf-8") 会抛出 UnicodeDecodeError: 'utf-8' codec can't decode byte 0xe9 in position 3: unexpected end of data。不要把它推广成"所有 Latin-1 文件按 UTF-8 解码都会失败":纯 ASCII 文件在两种编码下解码结果相同,而且有些字节序列在两种编码下都有效却表示不同的文本(b'caf\xc3\xa9' 在 UTF-8 下是 café,在 Latin-1 下是 café),所以解码成功并不能证明编码正确。修复方法是写明真实编码——绝不用 errors="ignore",那会悄悄丢弃无法解码的字节、掩盖问题。

Practice & Prediction Log

给定行 ana,7 / bo,x9 / cy,5(每行一条),运行前先预测容错读取器的结果:哪些行进入 scores,哪些行号进入 skipped。然后从任意目录运行这个完整程序;它会把自己的 scores.txt 夹具写入一个临时目录(结束后删除),并在循环之前定义 scores 和 skipped。try 块只包住 int(raw),并且只在解析成功后才保存分数。

import tempfile
from pathlib import Path

with tempfile.TemporaryDirectory() as tmp:
path = Path(tmp) / "scores.txt"
path.write_text("ana,7\nbo,x9\ncy,5\n", encoding="utf-8")
scores = {}
skipped = []
with open(path, encoding="utf-8") as f:
for lineno, line in enumerate(f, 1):
line = line.strip()
if not line:
continue
name, _, raw = line.rpartition(",")
try:
score = int(raw)
except ValueError:
skipped.append(lineno)
continue
scores[name] = score
print(scores, skipped)

Independent Mastery Criterion

有效行都被处理、跳过的行号被记录,并能正确命名两种失败:UnicodeDecodeError 意味着修正声明的编码;ValueError 意味着某行内容无效。

Authoritative Source Resources & Provenance

linking PSF License Version 2

Python Unicode HOWTO

作者 / 机构: Python Software Foundation | 原始出处链接 ↗

对应章节 / 时间戳: Encodings, decoding, and the causes of UnicodeDecodeError

验证版本与日期: Python 3.12 documentation; verified 2026-10-30

入选理由:

Primary explanation of byte-to-text decoding so learners fix the encoding instead of hiding errors.

来源前置条件:

安全回退方案(离线弹性):

The fixture bytes that trigger UnicodeDecodeError ship with the exercise, so the behavior is observable offline.

对比候选资源与选型分析 (3)

Established motivation for encodings, but redistribution rights are unclear and it does not document Python's decode behavior.

Unicode Standard (unicode.org) official documentation ↗ compared_alternative [Unicode License Agreement / Terms of Use]

Exhaustive normative standard but far too deep for a first contact; the Python HOWTO teaches the same code point/encoding distinction in executable form.

Strong motivation and history but pre-Python-3 and not runnable; retained as optional background reading only.

04 120 分钟

独立的文件汇总与恢复计划

独立掌握检查(不使用 AI 协助)。

Prerequisites

完成上一个活动;如遇卡壳,请使用列出的恢复链接。

Learning Objective

闭卷实现 summarize_scores(path),返回 {"scores": {name: 最高分}, "skipped": [行号], "missing": bool}。它必须能处理有效文件、含畸形行的文件和缺失文件而不崩溃。

Instructions

编码前先预测三个夹具的结果,检查期间不看答案、不用 AI。卡住时的恢复链接:缺失文件看 day-10-act-02,畸形行看 day-10-act-03。

Practice & Prediction Log

预期契约(在全新的临时目录中创建夹具,例如用 tempfile.TemporaryDirectory(),这样其他文件无法干扰):内容为 ana,7 / bo,9 / ana,8 的 s1.txt 返回 {'scores': {'ana': 8, 'bo': 9}, 'skipped': [], 'missing': False},因为 ana 的 8 胜过 7。内容为 ana,7 / bo,x9 / cy,5 的 s2.txt 返回 {'scores': {'ana': 7, 'cy': 5}, 'skipped': [2], 'missing': False},因为第 2 行是畸形的。该临时目录中从未创建过的路径(例如 missing.txt)返回 {'scores': {}, 'skipped': [], 'missing': True},而不是抛出异常。

Independent Mastery Criterion

独立完成时三个夹具都精确返回预测的字典;畸形行按行号报告;缺失文件返回 missing 标志而不是 traceback。

Authoritative Source Resources & Provenance

linking PSF License Version 2

The Python Tutorial: Input and Output

作者 / 机构: Python Software Foundation | 原始出处链接 ↗

对应章节 / 时间戳: Section 7.2 (Reading and Writing Files)

验证版本与日期: Python 3.12 documentation; verified 2026-10-30

入选理由:

Primary reference retained for the independent file-processing check.

来源前置条件:

安全回退方案(离线弹性):

The assessment runs entirely on local fixtures and the installed interpreter.

对比候选资源与选型分析 (2)
Real Python: Reading and Writing Files ↗ compared_alternative [Proprietary (free and paid tiers)]

Supplementary only; the assessment targets behavior, not tutorial recall.

io — Core tools for working with streams ↗ compared_alternative [PSF License Version 2]

Stream-layer reference for advanced cases; the tutorial chapter covers the open()/with patterns the drills require.