Exceptions and file handling
Organize a small program into modules and classes while handling files, failures and iteration.
THE IDEA
Errors are values you can respond to
When something goes wrong Python raises an exception. If nothing handles it, it travels up through the callers until the program stops with a traceback. A try statement lets you handle the ones you can actually do something about. Every exception has a type, and you choose which types a handler accepts.
def read_age(text): try: age = int(text) except ValueError: print("except") age = None else: print("else") finally: print("finally") return agePrinted: else, finally . Outcome: returns 42.
tryholds the risky line and nothing else you do not need.except SomeErrorruns if (and only if) that type of error was raised in the try block.elseruns only when the try block raised nothing. Good for code that depends on success.finallyruns always, handled or not, so it is the place for cleanup.
Predict first. With except ValueError, what happens for read_age(None)?
Catch what you can recover from
A parser that gets "cat" can reasonably fall back to a default: that is a ValueError and you expected it. A TypeError or a NameError means the code itself is wrong. If you catch those too, the program continues with corrupt or missing data and the bug resurfaces somewhere far away.
Name the specific exception: except ValueError:. Catch it as close as possible to the risky line.
Write except: or except Exception: pass to make an error message go away.
Files and the with statement
A file is a resource: opening it asks the operating system for a handle, and the handle must be released. If an error happens between open() and close(), the close is skipped. A with block closes the file when the block ends, however it ends.
try: f = open("notes.txt", "w") f.write("hello") 1 / 0 # an error before close() f.close()except ZeroDivisionError: print("error caught")print("closed?", f.closed)Press Run to see what Python really prints.
with open("scores.txt") as f:
for line in f: # one line at a time
print(line.strip()) Predict first. A file is opened with with open("n.txt", "w") as f: and an error happens halfway through the block. Is f.closed True afterwards?
Chapter context
As a program grows, the difficult part becomes keeping state and responsibilities understandable. Use simple functions for transformations, classes for related state and behavior, and modules to give those responsibilities names.
TRY IT YOURSELF
Exercise 1: catch only what you can handle
- Write
safe_int(text, default=None). It returnsint(text), and when the text is not a valid integer (intraisesValueError) it returnsdefaultinstead. - Catch **only**
ValueError. If someone passesNone,int(None)raisesTypeError, which is a real bug in the caller, and it must still propagate. - No bare
except:and noexcept Exception: they would hide the bug.
Exercise 2: read and write a file safely
save_scores(path, scores)writes one integer per line, replacing any old content.load_scores(path)reads them back as a list of ints.- Always open files with
with open(...) as f:so they are closed even when something fails. load_scoresreturns[]when the file does not exist (catchFileNotFoundErroronly). It skips blank lines. A line that is not a number is corrupt data: let theValueErrorpropagate instead of hiding it.
Both exercises must be solved to finish this lesson.
Stuck, or something not working? You can come back to it any time.
CHECK YOUR UNDERSTANDING
Which exception should a numeric parser normally catch for int('cat')?
Chapter reference: math, worked example & code
This section belongs to the whole chapter, so it is the same on every lesson in it and may cover a different lesson's topic. The code for the experiment you just ran is inside the experiment itself.
Math and notation
Reason about state ownership before adding abstraction. For a transformation f, repeated calls should have independent results unless shared state is explicitly intended. A lazy iterator retains only the state required for its next step; it does not imply that downstream consumers also use constant memory.
Worked example
An inventory starts with 5 items. reserve(2) leaves 3. A second independent inventory still contains 5. If both objects instead refer to the same list, mutating one changes what both observe. Draw object references as arrows to make this distinction explicit.
Reference code
The following example uses the Python standard library and does not call a remote service.
class Inventory:
def __init__(self, count):
self.count = count
def reserve(self, n):
if n > self.count:
raise ValueError("insufficient stock")
self.count -= n
first, second = Inventory(5), Inventory(5)
first.reserve(2)
assert (first.count, second.count) == (3, 5)
Python examples are provided for study and local execution; this page does not execute Python.
Common mistakes
Type annotations are not validation. Catching Exception and continuing can hide corrupt state. Lazy iteration does not make a later list(...) memory-free.