3.9 KiB
title | slug | date | tags | ||
---|---|---|---|---|---|
Two strategies for handling exceptions in Python | /two-strategies-for-handling-exceptions-in-python/ | 2023-10-03 |
|
The following function illustrates two strategies for handling exceptions in Python:
def parse_articles(articles: Dict[str, Any]) -> List[List]:
if not articles:
raise ValueError("No articles to parse")
articles_list = []
for article in articles.values():
try:
time_added, given_title, resolved_url = (
article["time_added"],
article["given_title"],
article["resolved_url"],
)
except KeyError as e:
logging.warning(
f"Article missing {e} property. Skipping article: {given_title}."
)
continue
articles_list.append([time_added, given_title, resolved_url])
return articles_list
The function is a basic transformer: it receives a dictionary and loops through a subset of its properties which are also dictionaries, returning selected keys from each as a multidimensional array.
The first exception handler checks whether the articles
dictionary is
populated or None
. If either are the case, the function exits and returns a
ValueError
. This approach propagates the error and is deliberately and
transparently obstructive: the function exits before it can execute the main
code. If this exception is not adequately handled by the caller, it will cause a
runtime error.
The second instance of exception handling occurs in the loop. If any of the
three specified properties (time_added
, given_title
, resolved_url
) are
absent from the articles
dictionary, a KeyError
exception will be thrown.
The outcome is quite different from the first exception however. The function
will not exit immediately, it simply won't append this set of properties to the
list, and will move on to the next iteration of the loop.
This behaviour, known as graceful degradation is afforded by the try, except
scaffolding. It literally allows us to try
an execution before an exception
can be raised. And if an exception is raised, we can augment the code with a
finally
block to return something regardless of the exception, such as an
empty list.
In the scenario above, finally
isn't used since the continue
ensures that
the execution proceeds to the next iteration of the loop. In addition, even if
none of the articles
contain the requisite keys, an empty list will be still
be returned.
Both strategies, propagation and graceful degradation, have their merits.
Propagation is useful because it identifies the error immediately and blocks the code execution, meaning that failures do not occur silently or go unchecked, forcing the caller to implement a handling routine. This is particulaly useful for genuine exceptions - cases that should not occur in regular execution. If they occur, they are so severe that they should be addressed immediately, not deferred. One such scenario might be a memory-intensive script or lambda running in a severless cloud provider: you would not want it to waste compute time on a process that is not going to return the expected value.
The obvious benefit of graceful degradation is that it doesn't halt the program straight away. This is most suitable for cases where the exception in question is fairly common. In the context of the code example this would be not returning articles that lack the requisite keys. Providing most of the articles are parsable, the fact that some are skipped is unlikely to be a terminal fault in the application.
Finally, the two methods differ in the amount of work required of the developer. Propagation requires explicit error handling from the function or method that receives the error. In contrast, graceful degradation allows for silent failure - the error is not immediately apparent and may only be detected in logs at a later stage.