Silently Handle Errors with contextlib.suppress – A Simple yet Powerful Python Pattern
Why Ignoring Errors Can Be a Good Thing
When I started writing scripts that touched the filesystem, I quickly discovered that dealing with missing files, permission issues, or temporary lock‑outs made every utility function feel like a minefield of try/except blocks. I wanted a way to tell Python, "Hey, if this particular thing doesn’t exist, just move on – it’s expected in production." The answer arrived in the standard library: contextlib.suppress.
By suppressing specific exceptions, you can keep your code clean, reduce visual noise, and focus on the happy path. This pattern is especially handy when you have a series of operations that are optional – for example, attempting to delete a temporary file that may already be gone, or trying to import a module that is only present in certain environments.
Introducing contextlib.suppress
contextlib.suppress is a tiny context manager that lets you wrap a block of code and silently ignore one or more exception types. Internally it catches the listed exceptions and simply re‑raises anything else, which means you get granular control without the boilerplate of a full try/except.
Use suppress when you have a clear, expected failure mode and you don’t need to inspect the exception details. It’s a lightweight way to keep your main flow readable.
Under the hood, suppress creates a try/except inside a __enter__/__exit__ pair, so you can still benefit from stack traces for unexpected errors while keeping the expected ones out of your logs.
Real‑World Example: Safe File Cleanup
Imagine a script that downloads a package, extracts it to a temporary directory, runs a build, and then cleans up. The cleanup step may need to delete the directory, but if the directory has already been removed by another process, you don’t want the script to abort. Using suppress makes the cleanup atomic and silent.
Here’s the pattern I use in production code:
import shutil
import tempfile
from contextlib import suppress
def clean_up(path: str) -> None:
"""Remove a directory, ignoring FileNotFoundError."""
with suppress(FileNotFoundError):
shutil.rmtree(path)
# In the script
tmp_dir = tempfile.mkdtemp()
# ... do work ...
clean_up(tmp_dir)
The clean_up function is short, explicit, and easy to test. If the directory disappears for any other reason – say, a permission error – the exception will bubble up, alerting you to a genuine problem. That balance between ignoring the expected and surfacing the unexpected is exactly why I prefer suppress over a blanket try/except.
Code Walk‑through
Let’s break down the snippet:
- import shutil, tempfile, suppress – standard modules; suppress is the star.
- def clean_up(path: str) -> None – a type‑annotated helper that signals its intent.
- with suppress(FileNotFoundError): – the context manager tells Python to swallow only
FileNotFoundErrorinside the block. - shutil.rmtree(path) – the actual operation. If the path is missing, suppress handles it; any other exception propagates.
You can also suppress multiple exception types by passing them as a tuple: with suppress(FileNotFoundError, PermissionError):. This is handy when you anticipate a few different failure modes but still want to keep the block tidy.
When Not to Suppress
While suppress is elegant, it’s not a silver bullet. I avoid using it when:
- You need to inspect the exception for logging or debugging. In that case, a regular try/except gives you access to the object.
- The error is a symptom of a larger logic flaw. Silently ignoring such issues can hide bugs that should be surfaced early.
- You’re catching
Exceptionbroadly. That defeats the purpose of Python’s explicit error handling.
Always ask yourself: Is this failure truly expected and inconsequential to the caller? If the answer is yes, suppress is a clean fit.
Advanced Patterns with Suppress
Beyond simple cleanup, suppress can be combined with other context managers to build sophisticated utilities:
from contextlib import suppress
from io import StringIO
import sys
def capture_stdout(func, *args, **kwargs):
"""Run func while capturing its stdout, ignoring any errors from writing."""
out = StringIO()
with suppress(OSError): # In case the underlying buffer fails
with redirect_stdout(out):
result = func(*args, **kwargs)
return result, out.getvalue()
Here I used suppress to guard against a rare OSError that can happen when the temporary buffer is closed prematurely. The pattern still lets the original function’s exceptions propagate, preserving the usual error‑reporting behavior.
Another handy trick is to pair suppress with a custom exception that carries metadata, allowing you to log suppressed events in a test‑friendly way:
from contextlib import suppress
import logging
logging.basicConfig(level=logging.INFO)
def optional_operation():
with suppress(ValueError):
# This may raise ValueError under certain conditions
raise ValueError("Missing configuration")
logging.info("Optional operation was skipped – configuration missing")
optional_operation()
In this example, the suppressed ValueError is not logged, but you can add a wrapper that logs before suppression if you need visibility.
Wrapping Up
contextlib.suppress is a deceptively simple tool that can shave dozens of lines off repetitive error‑handling boilerplate. By making the “expected failure” explicit in the context manager signature, you keep your code readable and maintainable. I rely on it daily for clean‑up tasks, optional imports, and graceful degradation in production scripts.
Try integrating suppress into a routine you currently wrap in try/except, and notice how the surrounding logic becomes clearer. You’ll find that the pattern not only reduces noise but also encourages you to think about which errors truly deserve to be ignored and which deserve to bubble up.