10 / 90

Day 10: Files, paths, and encodings

Turn fragile file reads into predictable programs: anchor paths to the script, name the encoding, release resources with with, and diagnose missing files and invalid content from evidence. 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 min

Paths, encodings, and safe file reads

Assistance allowed only after prediction and observed evidence are recorded.

Prerequisites

Complete the previous activity; use the listed recovery link if blocked.

Learning Objective

Read a text file with an explicit UTF-8 encoding inside a with block and count its non-empty lines. Predict the count before running, then explain why a relative path depends on the current working directory.

Instructions

Study source: within this activity's existing reading/practice time, use Automate the Boring Stuff with Python, 3rd edition, Chapter 10; Practice Questions; Appendix B answers, then explain one connection to the local task in your own words. Keep the local exercise and acceptance checks; use its original explanation if the external source is unavailable. In a fresh directory create notes.txt containing exactly three lines: alpha, beta, gamma. A relative path such as open("notes.txt") is resolved against Path.cwd(), not against the folder that holds the script. Iterating the file object yields one line at a time; each line keeps its "\n" terminator except possibly the last one, which has none when the file does not end with a newline (a file written as "alpha\nbeta\ngamma" yields 'gamma' as its final line). line.strip() removes that newline and any surrounding whitespace, so it decides whether a line is empty. The with block is predefined cleanup: it closes the file even if reading raises an exception.

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)
Before running, write down the exact line this prints and explain why each of the two values comes out the way it does. Would the result change if notes.txt did not end with a newline?

Independent Mastery Criterion

Your prediction matches the observed output, you state that relative paths resolve against Path.cwd(), and you show the file object's closed state (f.closed) after the with block exits.

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

Author: Al Sweigart | Original Source Link ↗

Relevant Section / Range: Chapter 10; Practice Questions; Appendix B answers

Verified Version & Date: Public edition checked 2026-11-01; moving pages may change

Selection Rationale:

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

Source Prerequisites:

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

Safe Fallback (Offline Resilience):

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

Compared Candidate Sources & Selection Analysis (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

Author: Python Software Foundation | Original Source Link ↗

Relevant Section / Range: Section 7.2 (Reading and Writing Files)

Verified Version & Date: Python 3.12 documentation; verified 2026-10-30

Selection Rationale:

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

Source Prerequisites:

Safe Fallback (Offline Resilience):

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

Compared Candidate Sources & Selection Analysis (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 min

Diagnose a missing file before editing code

Assistance allowed only after prediction and observed evidence are recorded.

Prerequisites

Complete the previous activity; use the listed recovery link if blocked.

Learning Objective

Reproduce FileNotFoundError from open("data/notes.txt") and classify the cause as an absent file or a wrong working directory by gathering Path.exists() and Path.cwd() evidence before editing any code.

Instructions

Work only in a disposable fixture: the scripts in this activity build project/ (containing reader.py and data/notes.txt) and an empty sibling folder outside/ inside a fresh temporary directory that is deleted on exit, so the result never depends on what happens to exist in your home directory or real projects. The message [Errno 2] No such file or directory: 'data/notes.txt' says only that the relative path did not resolve from the current working directory; it cannot separate the two causes. Gather evidence first: compare Path.cwd() with the script's folder, evaluate Path("data/notes.txt").exists() (relative to the cwd), and evaluate (BASE / "data" / "notes.txt").exists() with BASE = Path(__file__).resolve().parent (relative to the script). If the anchored path exists but the relative one does not, the working directory is wrong: open(BASE / "data" / "notes.txt", encoding="utf-8"). If the anchored path is also missing, the fixture is genuinely absent and must be created or restored; changing the code will not help.

Practice & Prediction Log

Run this script from any directory. It builds the disposable fixture, then runs the unmodified reader.py (which opens the relative path data/notes.txt) once with the project folder as the cwd and once with outside/ as the cwd. Before running, predict the exit code and every printed line of both runs. Then classify the cause from the evidence and write the minimal fix for reader.py without rewriting its counting logic.

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

Your log states which of the two causes applied, shows the cwd/exists() evidence that proves it, and applies the minimal fix (path anchor or fixture restore) instead of rewriting the reader.

Authoritative Source Resources & Provenance

linking PSF License Version 2

Python Standard Library: Built-in Exceptions

Author: Python Software Foundation | Original Source Link ↗

Relevant Section / Range: Concrete exceptions and Exception hierarchy

Verified Version & Date: Python 3.12 documentation; verified 2026-10-30

Selection Rationale:

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

Source Prerequisites:

Safe Fallback (Offline Resilience):

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

Compared Candidate Sources & Selection Analysis (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

Author: Python Software Foundation | Original Source Link ↗

Relevant Section / Range: Path.exists, Path.cwd, and anchoring paths to __file__

Verified Version & Date: Python 3.12 documentation; verified 2026-10-30

Selection Rationale:

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

Source Prerequisites:

Safe Fallback (Offline Resilience):

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

Compared Candidate Sources & Selection Analysis (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 min

Invalid content: tolerant parsing with a skip report

Assistance allowed only after prediction and observed evidence are recorded.

Prerequisites

Complete the previous activity; use the listed recovery link if blocked.

Learning Objective

Parse a scores file in which one row is malformed: record each skipped line number and keep going. Distinguish UnicodeDecodeError (bytes decoded with the wrong encoding) from ValueError (valid text, invalid value).

Instructions

Rows look like ana,7. int("x9") raises ValueError: invalid literal for int() with base 10: 'x9'. Wrap only the int(raw) call in try/except ValueError, append the line number to a skipped list, and store the score only when parsing succeeded. Decoding problems are a different failure: the bytes b'caf\xe9' (café saved as Latin-1) are not valid UTF-8, so b'caf\xe9'.decode("utf-8") raises UnicodeDecodeError: 'utf-8' codec can't decode byte 0xe9 in position 3: unexpected end of data. Do not generalize this to "every Latin-1 file fails as UTF-8": a pure-ASCII file decodes identically under both, and some byte sequences are valid in both encodings but mean different text (b'caf\xc3\xa9' is café in UTF-8 but café in Latin-1), so a decode that succeeds does not prove the encoding is right. The fix is to name the real encoding — never errors="ignore", which silently drops undecodable bytes and hides the problem.

Practice & Prediction Log

Given rows ana,7 / bo,x9 / cy,5 (one per line), predict the result of a tolerant reader before running: which rows end up in scores, and which line numbers end up in skipped. Then run this complete program from any directory; it writes its own scores.txt fixture into a temporary directory that is deleted afterwards and defines scores and skipped before the loop. Keep the try block limited to int(raw), and store a score only after parsing succeeded.

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

Valid rows are processed, skipped line numbers are recorded, and you correctly name both failure types: UnicodeDecodeError means fix the declared encoding; ValueError means one row's content was invalid.

Authoritative Source Resources & Provenance

linking PSF License Version 2

Python Unicode HOWTO

Author: Python Software Foundation | Original Source Link ↗

Relevant Section / Range: Encodings, decoding, and the causes of UnicodeDecodeError

Verified Version & Date: Python 3.12 documentation; verified 2026-10-30

Selection Rationale:

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

Source Prerequisites:

Safe Fallback (Offline Resilience):

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

Compared Candidate Sources & Selection Analysis (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 min

Independent file summary and recovery plan

Independent Mastery Check (no AI assistance).

Prerequisites

Complete the previous activity; use the listed recovery link if blocked.

Learning Objective

Closed-book: write summarize_scores(path) returning {"scores": {name: best}, "skipped": [line numbers], "missing": bool}. It must handle a valid file, a file with a malformed row, and a missing file without crashing.

Instructions

Predict all three fixture results before coding, and do not consult an answer or AI during the check. Recovery links if blocked: day-10-act-02 for a missing file, day-10-act-03 for a malformed row.

Practice & Prediction Log

Expected contract (create the fixtures in a fresh temporary directory, for example with tempfile.TemporaryDirectory(), so no other file can interfere): s1.txt containing ana,7 / bo,9 / ana,8 returns {'scores': {'ana': 8, 'bo': 9}, 'skipped': [], 'missing': False} because 8 beats 7 for ana. s2.txt containing ana,7 / bo,x9 / cy,5 returns {'scores': {'ana': 7, 'cy': 5}, 'skipped': [2], 'missing': False} because line 2 is malformed. A path inside that temporary directory that was never created, such as missing.txt, returns {'scores': {}, 'skipped': [], 'missing': True} instead of raising.

Independent Mastery Criterion

Unaided, all three fixtures return exactly the predicted dictionaries, the malformed row is reported by line number, and the missing file returns the missing flag rather than a traceback.

Authoritative Source Resources & Provenance

linking PSF License Version 2

The Python Tutorial: Input and Output

Author: Python Software Foundation | Original Source Link ↗

Relevant Section / Range: Section 7.2 (Reading and Writing Files)

Verified Version & Date: Python 3.12 documentation; verified 2026-10-30

Selection Rationale:

Primary reference retained for the independent file-processing check.

Source Prerequisites:

Safe Fallback (Offline Resilience):

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

Compared Candidate Sources & Selection Analysis (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.