No Naked Excepts


You probably shouldn’t be using except: or except Exception: in Python, sometimes called ‘catch-all exceptions’ or ‘naked excepts’.

I’ve tried to find a good article or blog post discussing this, but I haven’t found any thorough discussions of the problems that arise.

Let’s take a look at exceptions. Python is excellent at throwing exceptions, and errs on the side of more exceptions rather than less. For example, this code will raise KeyError:

d = {'foobar': 'Hello world'}
print d['fooba'] # spelling mistake!

The equivalent code in other languages will just return a sentinel value. For example, JavaScript:

var d = {'foobar': 'Hello world'};
console.log(d['fooba']); // prints 'undefined'

The main disadvantage of the JavaScript approach is that it’s easy to get confused when you have your sentinel value in the hashmap. It’s hard to distinguish {} from {'foobar': undefined}.

So, there are a large number of situations that will throw exceptions in Python.

Let’s consider the following piece of Django code, which fetches a page, if it exists.

try:
    page = Page.objects.get(name="home")
except Page.DoesNotExist:
    page = None

This works as expected. However, can you see the mistake in this version?

try:
    page = Page.object.get(name="home")
except:
    page = None

This code will actually throw AttributeError, since Page.object doesn’t exist. However, since we used a catch-all except, we don’t get any error. Subtle bugs like these can be a pain to track down later.

The solution is to always name the specific exception you’re expecting. Note there’s a nasty gotcha with catching multiple exceptions:

try:
    do_something()
except SomeException, OtherException:
    # This only catches SomeException and binds it to
    # the variable OtherException
    pass

The correct way to write this is:

try:
    do_something()
except (SomeException, OtherException):
    # the tuple ensures we catch both exceptions
    pass

To avoid this gotcha completely, you can either use the excellent Pyflakes to check your code, or use the as syntax:

try:
    do_something()
except (SomeException, OtherException) as e:
    # not using a tuple here is a syntax error
    pass

As with every rule, there are a few exceptions. The first is when you’re allowing exceptions to propagate.

try:
    some_resource = open_resource()
    process_resource(some_resource)
except:
    # cleanup resource before propagating exception
    some_resource.close()
    # re-raise the original exception
    raise

The other case is long running process, such as daemons:

while True:
    try:
        do_daemon_stuff()
    except:
        log_exception_somewhere()

In this case, you only want a naked except at the very top level of your program, and you want the exception to go somewhere. I like to log to Sentry or to a log file monitored by Papertrail.

To sum up: Only catch the exceptions you’re expecting to see. For all other exceptions, you want to be informed about it so you can fix it.

Recent Posts

Pattern Matching in Emacs Lisp

These Weeks in Remacs

This Week in Remacs

Announcing Remacs: Porting Emacs to Rust

Introspecting Glue Code