0%

Reading and Writing Files · practice

Make File Failures Actionable

read_books works when its agreement is satisfied: the path exists, names a readable file, and contains UTF-8 text. Outside-world boundaries sometimes break that agreement.

Two failures are expected enough to help with here:

  • FileNotFoundError: nothing exists at the path Python tried to open;

  • UnicodeDecodeError: bytes exist, but they are not valid UTF-8 text.

Neither means read_books should return an empty . An empty file is valid and genuinely contains no titles. A missing or undecodable file is a different state, and disguising it as “zero books” would create a believable lie.

Keep the reusable function honest

Let read_books(path) raise these . The has no user interface and cannot know whether its caller is a program, a later data converter, or a test. Returning a special sentence from a list-producing function would mix two jobs.

Catch the failures in main(), where the program finally has enough context to tell a person what to do:

try:
    books = read_books(BOOKS_PATH)
except FileNotFoundError:
    print(f"Could not find {BOOKS_PATH}. Run this program from the project root.")
    return
except UnicodeDecodeError:
    print(f"Could not read {BOOKS_PATH} as UTF-8 text.")
    return

After the two handlers, print Loaded 4 books. using the actual length. Later lessons will replace that temporary success message with durable output.

Catch only what you can repair

except Exception: would also catch a misspelled , broken arithmetic, or a bug introduced tomorrow. The program could print “file problem” even when the file is perfect. That makes the original disappear at the moment it is most useful.

Narrow handlers are not about making a program never fail. They are about improving failures whose meaning you understand while leaving unfamiliar failures visible.

The error text should include BOOKS_PATH. “File not found” names a category. “Could not find data/books.txt; run from the project root” gives a next move and connects back to the path rule from the first lesson.

Why should a missing file not become an empty list?

Implement the two narrow handlers and the temporary success message.

Then check that you did not overreach. Temporarily misspell a variable name inside read_books so it raises a NameError, and run the program. You want a traceback. If instead you see one of your two friendly file messages, the handler is catching more than it understands. Put the name back afterwards.

If you are unsure whether a handler is too wide, ask Monty: “What could except Exception catch here that I would rather see as a traceback?” The list is usually longer than people expect.

Task

Update main() so it:

  • calls read_books(BOOKS_PATH);

  • catches FileNotFoundError, prints a message containing BOOKS_PATH and the project-root next step, then returns;

  • catches UnicodeDecodeError, prints a message containing BOOKS_PATH and UTF-8, then returns;

  • otherwise prints Loaded N books. using the real length.

Do not catch Exception or other unrelated programming errors.