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.