Errors should never pass silently

Posted by Bob on Wed 18 January 2017 in Best practices • 3 min read

>>> import this
The Zen of Python, by Tim Peters

Explicit is better than implicit.
...
Errors should never pass silently.

Anti-Patterns

This is a great read: The Little Book of Python Anti-Patterns. For the more experienced Pythonistas most is well known, yet it is a good refresher and you probably still find something new.

Today a bit about error handling. In our Learning from Python mistakes article we already mentioned not to use pass in except. It is actually the worst anti-pattern (as stated by Andreas Dewes, the author of the book, you can listen to the interview here).

The problem with except: pass

Why is it so bad? See SO for a detailed explanation:

As you correctly guessed, there are two sides to it: Catching any error by specifying no exception type after except, and simply passing it without taking any action.

My explanation is “a bit” longer—so tl;dr it breaks down to this:

  1. Don’t catch any error. Always specify which exceptions you are prepared to recover from and only catch those.
  2. Try to avoid passing in except blocks. Unless explicitly desired, this is usually not a good sign.

...

The worst offender though is the combination of both. This means that we are willingly catching any error although we are absolutely not prepared for it and we also don’t do anything about it.

So this violates the two Zen aphorisms above. You always want to catch errors explicitly:

>>> try:
...     1/0
... except ZeroDivisionError:
...     print('cannot divide by 0')
... 
cannot divide by 0

You can use else and finally with a try/except as shown in this toy example:

>>> a, b, c = 1, 2, 0
>>> try:
...     a/b
... except ZeroDivisionError:
...     print('cannot divide by 0')
... else:
...     print('division was ok')
... finally:
...     print('this always runs')
... 
0.5
division was ok
this always runs
>>> try:
...     b/c
... except ZeroDivisionError:
...     print('cannot divide by 0')
... else:
...     print('division was ok')
... finally:
...     print('this always runs')
... 
cannot divide by 0
this always runs

One thing to watch out for is except clause order if you have more than one: always go from more specific to more generic (bottom to top in the inheritance chain), for example:

>>> ZeroDivisionError.__mro__
(<class 'ZeroDivisionError'>, <class 'ArithmeticError'>, <class 'Exception'>, <class 'BaseException'>, <class 'object'>)

(about mro)

Asking for permission instead of forgiveness

It becomes even more important because the Python community uses an EAFP (easier to ask for forgiveness than permission) coding style.

I have done this a lot:

if os.path.exists("file.txt"):

But the Pythonic way to do it is:

try:
    # assume the file is there
    os.unlink("file.txt")
except OSError:
    # if not, handle the (explicit) error

Hence, more reason to manage exceptions well!

Custom exceptions

Another great way to make your code more readable and taking exceptions to the next level is to write your own. Sounds scary? It is actually pretty easy as this great post shows:

class NameTooShortError(ValueError):
    pass

def validate(name):
    if len(name) < 10:
        raise NameTooShortError(name)

A bit more involved (yet still easy to follow) example from tweepy:

class TweepError(Exception):
    """Tweepy exception"""

    def __init__(self, reason, response=None, api_code=None):
        self.reason = six.text_type(reason)
        self.response = response
        self.api_code = api_code
        Exception.__init__(self, reason)

    def __str__(self):
        return self.reason

Which we used in our Twitter bot:

def post_tweet(self, status):
    try:
        self.api.update_status(status)
        logging.debug('posted status {} to twitter'.format(status))
    except TweepError as err:
        logging.error('tweepy update_status error: {}'.format(err))

Reference

Recommended reading:


See an error in this post? Please submit a pull request on Github.

Join our community and grab our Become a Better Python Developer cheat sheet. Learn Python. Receive bonus material. Challenge yourself!