beginner25 min

Files and Exceptions

Handle errors gracefully with try/except/finally and raise, and understand how reading and writing files works.

What you'll learn

  • Describe conceptually how open(), read, and write work with files
  • Handle errors with try/except/finally
  • Raise an exception deliberately with raise

Prerequisites

Explanation

Two things every real program eventually needs: reading and writing data that outlives the program itself, and handling the moments when something goes wrong.

Reading and writing files. On a normal computer, open("data.txt", "w") opens a file for writing (creating it if needed), and calling .write(text) on the result sends text into it; open("data.txt", "r") opens it for reading, and .read() or looping over the file line by line pulls the text back out. Files should generally be closed once you're done — usually with a with open(...) as f: block, which closes the file automatically even if an error happens inside it.

Simulating files in this sandbox. This lesson's runner executes Python in a sandboxed environment with no persistent disk, so it can't open real files on your computer. To still practice the behavior of file I/O, the examples below use io.StringIO, a built-in class that behaves like an open file but stores its contents in memory instead of on disk. Everything you do with it — .write(), reading lines, .seek(0) to "rewind" back to the start before reading — mirrors what you'd do with a real file object; only the storage location differs.

Exceptions. When something goes wrong at runtime — converting text that isn't a number, dividing by zero, looking up a key that doesn't exist — Python raises an exception, which stops the program unless something catches it. A try/except block lets you catch a specific exception type and recover instead of crashing:

try:
    value = int(user_input)
except ValueError as error:
    print("That wasn't a number:", error)

Catching a specific exception type (like ValueError) rather than a bare except: is important — a bare except silently swallows every kind of error, including ones you never anticipated and would rather see fail loudly.

finally. An optional finally block runs no matter what happened in the try — whether it succeeded, raised a handled exception, or even raised one that wasn't caught. It's the natural place for cleanup that must always happen, such as closing a file or a network connection.

raise. You can trigger your own exception with raise, typically to reject invalid input to a function before it causes confusing behavior further down the line: raise ValueError("age cannot be negative"). Combined with a descriptive message, this makes bugs far easier to track down than letting invalid data silently propagate and fail somewhere unrelated.

Together, file I/O and exception handling are what let a program interact with the messy outside world — user input, other systems, files — without falling over the first time something isn't exactly as expected.

Example

io.StringIO stands in for a real file since this sandbox has no persistent disk; try/except/finally handles a bad conversion.

import io

# io.StringIO acts like an in-memory file, standing in for a real file on disk.
fake_file = io.StringIO()
fake_file.write("apple,3\n")
fake_file.write("banana,5\n")

fake_file.seek(0)  # rewind to the beginning, like reopening a file for reading

for line in fake_file:
    name, count = line.strip().split(",")
    print(f"{name}: {count} units")

try:
    value = int("not-a-number")
except ValueError as error:
    print("Could not convert:", error)
finally:
    fake_file.close()
    print("Cleanup complete.")

Try it yourself

Add a third line to fake_file before it's rewound, then press Run.

Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.

Loading editor…

Guided exercise

Guided exercise

Complete safe_divide so it raises ValueError('Cannot divide by zero') when b is 0, and otherwise returns a / b.

Checks: safe_divide(10, 2) returns 5.0 · safe_divide(5, 0) raises ValueError · plus 1 hidden check

Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.

Loading editor…

Stuck? Get a hint.

Independent exercise

Independent exercise

Write parse_scores(text), which parses newline-separated 'name,score' lines (as if read from a file) into a dict, skipping any malformed line using try/except instead of crashing.

Checks: malformed lines are skipped while valid ones are kept · fully valid input parses completely · plus 1 hidden check

Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.

Loading editor…

Stuck? Get a hint.

Common mistakes

  • Catching a bare except: (every possible error), which can hide real bugs that have nothing to do with the case you meant to handle.
  • Assuming a file opened for writing with 'w' will be automatically closed without a with block or an explicit .close() call.
  • Forgetting that finally runs even when the try block returns or raises an exception that isn't caught — it's not just for the 'happy path'.
  • Expecting normal filesystem access to work in every Python environment; sandboxed and browser-based runtimes often have no real disk at all.

Knowledge check

Knowledge check

1. What is guaranteed about code inside a finally block?
2. What does the raise statement do?
3. Why is except ValueError: usually preferred over a bare except::
4. Why does this lesson use io.StringIO instead of opening a real file?

Takeaway

try/except lets you recover from anticipated errors, finally guarantees cleanup code always runs, and raise lets your own functions reject bad input clearly.

Summary

Files are opened, read, and written with open() and methods like .read()/.write(), ideally inside a with block; this sandbox simulates that behavior with io.StringIO since it has no real disk. try/except catches specific exception types so programs can recover gracefully, finally guarantees cleanup runs regardless of outcome, and raise lets you deliberately signal that something has gone wrong.

References

Your notes

Notes save automatically.

Finished this lesson?

Mark it complete to track your progress and schedule a future review.

Next: Classes and Objects