...

/

Best Practices for Exception Handling

Best Practices for Exception Handling

Learn about the best practices that relate to exception handling in Python.

Here are some recommendations that relate to exceptions in Python.

Handling exceptions at the right level of abstraction

Exceptions are part of the principal functions that do one thing, and one thing only. The exception the function is handling (or raising) has to be consistent with the logic encapsulated on it.

In the code below, there's an example of what we mean by mixing different levels of abstractions. Imagine an object that acts as a transport for some data in our application. It connects to an external component where the data is going to be sent upon decoding. In the following listing, we will focus on the deliver_event method:

Press + to interact
main.py
base.py
import logging
import time
from base import Connector, Event
logger = logging.getLogger(__name__)
class DataTransport:
"""And example of an object handling exceptions of different levels."""
_RETRY_BACKOFF: int = 5
_RETRY_TIMES: int = 3
def __init__(self, connector: Connector) -> None:
self._connector = connector
self.connection = None
def deliver_event(self, event: Event):
try:
self.connect()
data = event.decode()
self.send(data)
except ConnectionError as e:
logger.info("connection error detected: %s", e)
raise
except ValueError as e:
logger.error("%r contains incorrect data: %s", event, e)
raise
def connect(self):
for _ in range(self._RETRY_TIMES):
try:
self.connection = self._connector.connect()
except ConnectionError as e:
logger.info(
"%s: attempting new connection in %is", e, self._RETRY_BACKUP,
)
time.sleep(self._RETRY_BACKUP)
else:
return self.connection
raise ConnectionError(f"Couldn't connect after {self._RETRY_TIMES} times")
def send(self, data: bytes):
return self.connection.send(data)

For our analysis, let's zoom in and focus on how the deliver_event() method handles exceptions.

What does ValueError have to do with ConnectionError? Not much. But by looking at these two highly different types of error, we can get an idea of how responsibilities should be divided.

The ConnectionError should be handled inside the connect method. This allows for a clear separation of behavior. For example, if this method needs to support retries, then handling the said exception would be a way of doing it.

Conversely, ValueError belongs to the decode method of the event. With this new implementation (shown in the next example), this method doesn't need to catch any exception—the exceptions we were worrying about before are either handled by internal methods or deliberately left to be raised.

We should separate these fragments into different methods or functions. For the connection management, a small function should be enough. This function will be in charge of trying to ...