NOETRION

Choose a topic

DAY 16 OF 30 · TRANSFORM AND ORGANIZE

Files and context managers

Read and write local text with explicit encoding, predictable modes, and reliable closing.

90–120 minutes suggested study timePython 3.12Not yet completed

Today's outcomes

  • Choose read, write, append, or exclusive creation.
  • Use with and pathlib for local files.
  • Preserve line boundaries and distinguish text from bytes.

Before you start: Complete Day 15 and its practical challenge.

Understand the concept

A file extends a program's data beyond one run

Variables disappear when a process ends unless data is saved elsewhere. A local file can preserve a report or input dataset for the next run. Reading and writing introduce new boundaries: file location, encoding, permissions, existing contents, and the representation used on disk.

Use pathlib to represent a filesystem path, and use a context manager when working with an open file. with arranges for the file to be closed when its block exits, including when an exception interrupts ordinary execution. That resource-management guarantee does not mean a partially written report is automatically rolled back.

Every example in this lesson uses a temporary directory that is removed afterward. This makes experiments repeatable and avoids accidentally replacing a personal file. For your own saved work, choose a dedicated practice directory and inspect the destination before using a destructive write mode.

File modes encode an intent

Read mode r opens an existing file for reading. Write mode w creates a file or truncates an existing one. Append mode a adds content at the end. Exclusive creation mode x refuses to replace an existing file. Add b for binary data; ordinary text mode decodes or encodes strings.

Appending is not the same as editing an existing record. Re-running an append script can duplicate the same report line. Re-running a write script can replace earlier content. Choose the operation from the persistence contract and test two runs, not just the initial creation.

Encoding and line boundaries need explicit rules

Use an explicit encoding such as UTF-8 for text you control. Encoding translates strings to bytes and back; it is not the same as Unicode normalization from text processing. A wrong encoding can fail or produce wrong characters before any cleaning function sees them.

Newline characters separate records in a simple line-oriented file. write does not automatically append a newline. read returns text including line boundaries. splitlines creates logical lines without their terminators by default. If blank lines are meaningful, do not delete them without a stated rule.

Reading an entire small file is clear for learning. A very large file may need line iteration or chunking. “This example works” does not imply loading every file into memory is the appropriate production strategy.

Worked example

from pathlib import Path
from tempfile import TemporaryDirectory

with TemporaryDirectory() as directory:
    path = Path(directory) / "study.txt"
    with path.open("w", encoding="utf-8") as handle:
        handle.write("lists: 30\n")
        handle.write("functions: 60\n")
    with path.open("a", encoding="utf-8") as handle:
        handle.write("strings: 45\n")
    with path.open("r", encoding="utf-8") as handle:
        text = handle.read()

    lines = text.splitlines()
    assert lines == ["lists: 30", "functions: 60", "strings: 45"]
    assert text.endswith("\n")
    print("Logical lines:", len(lines))
    print(text, end="")
Operation Effect on disk Evidence
Open w Fresh report content Earlier contents would be replaced
Two write calls Two newline-terminated records Each call supplies its newline
Open a and write One additional record Earlier two records remain
Read and splitlines Three logical lines Round-trip assertion

The report survives closing and reopening the file within the temporary directory. It does not survive the directory's final cleanup, which is intentional for this exercise.

Guided practice

  1. Predict: write two values without newline characters. What text will read return? Explain why two write calls do not inherently mean two lines.
  2. Modify: run the append step twice and inspect the duplicate final record. Decide whether your own log should permit duplicates or needs a stable event identity.
  3. Reconstruct: write one non-ASCII topic label such as café, read it back with UTF-8, and compare the exact string. Keep a test that includes the newline policy too.

A relative path is interpreted from the current working directory, which may differ between a terminal and an editor. If a file “disappears,” inspect where the program actually wrote it before assuming the write failed.

Daily challenge

Implement write_topics(path, topics) and read_topics(path). Topics are strings without embedded line breaks. Write one topic per line using UTF-8 and replacement semantics: the new list becomes the complete file content. Read the same list back. An empty list should produce an empty file and read back as an empty list.

Topics Round-trip result
['lists', 'functions'] Same two labels in order
['café'] Exact Unicode label preserved
[] Empty list
Write two topics, then write one Only the new single topic remains

Use a temporary directory for all acceptance cases. The contract does not permit newline characters inside a label because they would be confused with record separators. More general records need a structured format such as JSON on Day 25.

Reference solution

Compare after attempting the challenge
from pathlib import Path
from tempfile import TemporaryDirectory

def write_topics(path, topics):
    text = "".join(topic + "\n" for topic in topics)
    Path(path).write_text(text, encoding="utf-8")

def read_topics(path):
    return Path(path).read_text(encoding="utf-8").splitlines()

with TemporaryDirectory() as directory:
    path = Path(directory) / "topics.txt"
    for topics in [["lists", "functions"], ["café"], []]:
        write_topics(path, topics)
        assert read_topics(path) == topics
    write_topics(path, ["lists", "functions"])
    write_topics(path, ["strings"])
    assert read_topics(path) == ["strings"]
print("UTF-8 round trips passed")

The generator expression inside join supplies text pieces without needing a temporary list. Its role here is simple: one newline-terminated string per topic. pathlib's convenience methods handle opening and closing for these small whole-file operations.

Debug and explain

If old data disappears, inspect w mode. If records repeat, inspect a mode and repeated executions. If a label changes after saving, inspect encoding and newline assumptions. If a read raises FileNotFoundError, distinguish an absent file from an empty existing file.

Explain which operation in this challenge intentionally replaces earlier data, why the Unicode round-trip matters, and why with does not guarantee transaction rollback. Tomorrow you will handle known file and conversion failures without silently turning them into valid-looking empty results.

Review and next step

Keep the four round-trip cases and a note about current working directory. Rebuild the persistence contract before copying the solution. Day 17 adds explicit validation and targeted exceptions around these boundaries.

Sources and verification

Original teaching examples checked with Python 3.12.14. Library behavior reviewed against these primary references on October 3, 2026. Day 26's examples also use Beautiful Soup 4.14.3.

DAILY CHECKPOINT

Check your understanding

Five questions, 20 points each. Pass at 85% or above, which requires all five correct. Feedback explains each answer; revise the lesson and retry when needed.

01. Why use with when opening a file?

02. What does opening in w mode do to an existing file?

03. Why specify encoding for a text file?

04. What does a relative Path depend on?

05. What is a file round-trip test?

Review your practical work

Check these statements after running your own challenge. This is your self-review; the website does not execute or assess your Python code.

Pass the knowledge check and finish the practical self-review to save completion.