No Naked Excepts

03 November 2013

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.