Things I hate: naked python "except:"

Why can't I CTRL-C out of this script? Oh:

while True:
    try:
        x = do_the_thing()
        return x
    except:
        log('An error occurred, retry')

This Code Kills article gives a few good suggestions on how to avoid this.

One case that I've encountered a few times is this: I need to call some function that might fail. I don't particularly care how or why it failed, but I want to raise a new exception type, because higher level code only cares about what operation was attempted, not how it failed.

So I write something like this:

def print_document(doc, printer):
    try:
        conn = open_connection_to_printer(printer)
        conn.send_print_job(doc)
    except Exception:
        raise PrintError

Catching Exception is significant. This filters out things like KeyboardInterrupt and SystemExit, which prevents my original complaint about CTRL-C not working.

Because network connections can raise a lot of different error types, I don't really feel like trying to enumerate all the possible errors (plus, the next version of that module might add new ones).

But then a day comes when I'm sure that the network connection is okay, and I really need to diagnose why this code is failing. I wish I could see what happened at the moment I raised PrintError.

My improved code looks like this:

def print_document(doc, printer):
    try:
        conn = open_connection_to_printer(printer)
        conn.send_print_job(doc)
    except Exception as e:
        raise PrintError(err=e), None, sys.exc_info()[2]

That last line contains a bit too much opaque cleverness, but it does two important things:

  • It captures the original exception, so even though we propagate PrintError upwards, we can always dig into the inner err as needed (for example, PrintError's __str__ or __repr__ might print out err.
  • It preserves the original stacktrace. I didn't know I needed this until my code started failing with an unexpected PrintError, with the inner err containing, say, KeyError. By itself that's not enough information to tell what happened; I want to see where the KeyError was raised, but in my earlier code the stacktrace ends at print_document(). Using the long form of raise (reference) allows me to pass the original stacktrace instead.