Suppressing Exceptions in Python with contextlib.suppress, not try/except/pass
Tags: python
Published: 2020-09-28 13:10:07

When programming, it is common to perform an operation that is expected to fail at times, but should not throw an error. In Python, `try/except` clauses are frequent. In human terms, `try/except` means, "try this operation, and if it fails for _this_ reason, then do _this other thing_." For instance, you can try the following out in your console.

```python
friends = {"Nguyen": "tacos", "Hannah": "borscht"}


def get_food(friend_name):
    try:
        food = friends[friend_name]
    except KeyError:
        food = "baklava"
        friends[friend_name] = food
    return food


get_food("Nguyen")
# "tacos"

get_food("Ahmed")
# "baklava"

friends
# {'Nguyen': 'tacos', 'Hannah': 'borscht', 'Ahmed': 'baklava'}
```

The above in simple language: if the key isn't in the dictionary, don't fret, just add it with a default value.

What if you don't have an alternate course of action? What if you just want to silence the error? Let's say you want to make a directory, and if the directory is already there, don't whine about it, just ignore. Some developers use `try/finally` for this case as well:

```python
import os

os.mkdir("frivolous_directory")

try:
    os.mkdir("frivolous_directory")
except FileExistsError:
    pass
```

So, the above works, it is common practice, and one doesn't have to do any special imports. Awesome. No shame if you have good reason to use this pattern.

However, in human terms, the above `try/except` block reads like this: make the directory, but if that fails because it already exists, then perform the following action: um, nothing.

[![Crickets](https://media.giphy.com/media/l2R013mIf1ZXdvoyI/giphy.gif)](https://giphy.com/gifs/l2R013mIf1ZXdvoyI)

So, from a _utility_ standpoint, it works fine. From a _semantic_ standpoint, it is not particularly meaningful.

## A meaningful alternative: `contextlib.suppress`

If the desire is to simply suppress an error, rather than perform some sort of branching logic, the Python standard library has a paradigm for that: `contextlib.suppress()`. It works like this:

```python
import contextlib
import os

os.mkdir("frivolous_directory")

with contextlib.suppress(FileExistsError):
    os.mkdir("frivolous_directory")
```

The `FileExistsError` in the above was silenced.

This was two less lines of code than the previous `try/except` example (OK, we had to add `import contextlib` as well, so one less line in this example).

More importantly, it is very clear what is happening in the code. We don't mind if the operation fails for the specific reason given. There is no `pass` with unclear intent. We are not `try`ing something nor are we seeking to intercept the error for further logic or processing.

## Conclusion

This isn't so much about _working_ code as it is about _readable_ and _reasonable_ code. [Using `contextlib.suppress`](https://docs.python.org/3/library/contextlib.html#contextlib.suppress) is a worthwhile practice. [Reading a bit about the treasures in `contextlib`](https://docs.python.org/3/library/contextlib.html) may be worth your time as well!

[python]: https://python.org