Foundations: Python, Git and toolsLoading progress…

Exceptions and file handling

Organize a small program into modules and classes while handling files, failures and iteration.

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.

call
handler
def read_age(text): try: age = int(text) except ValueError: print("except") age = None else: print("else") finally: print("finally") return age
try: int("42") works
except ValueError: handle it
else: only when try raised nothing
finally: always runs
return age
not handled: the error escapes to the caller

Printed: else, finally . Outcome: returns 42.

Pick a call and a handler. The lit boxes are the path Python takes through try, except, else and finally.
  • try holds the risky line and nothing else you do not need.
  • except SomeError runs if (and only if) that type of error was raised in the try block.
  • else runs only when the try block raised nothing. Good for code that depends on success.
  • finally runs 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.

Do

Name the specific exception: except ValueError:. Catch it as close as possible to the risky line.

Don't

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.

Run each snippet in real Python (it uses an in-memory file system, but the calls are the same as on disk). Compare the two closed? lines.
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.

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.