It is commonly known that catching broad exceptions, such as Exception
in a try...except
block leads to issues around catching exceptions that should not have been caught and then are not handled correctly in the except
block. A related issue is encapsulating too much code in a try...except
block which can lead to catching exceptions on unexpected lines of code. This post provides an example where an except
block unexpectedly catches an exception and gives an example for how to avoid the issue.
Take a look at the following example where the try...except
block in the get_name_key
function encapsulates a local operation and a function call:
def check_id_valid(source: dict):
id_ = source["id"]
if not isinstance(id_, str) and not id_.isnumeric():
raise InvalidIdError()
def get_name_key(source: dict):
try:
check_id_valid(source)
name = source["name"]
except KeyError:
raise NameNotFoundError()
Calling get_name_key({})
will result in the NameNotFoundError
error being raised although not because the name
key is not in source
– it actually gets raised because id
is not in the source. That is confusing! There are a few changes we should make to the code to clean it up.
Firstly, the try...except
block raising NameNotFoundError
should be scoped to just include where KeyError
related to the name
key can actually occur:
def check_id_valid(source: dict):
id_ = source["id"]
if not isinstance(id_, str) and not id_.isnumeric():
raise InvalidIdError()
def get_name_key(source: dict):
check_id_valid(source)
try:
name = source["name"]
except KeyError:
raise NameNotFoundError()
Now we get the expected behaviour which is that a KeyError
is raised indicating that id
is not a key in source
. The second change we should make is to also handle KeyError
in the check_id_valid
to function to improve the developer experience:
def check_id_valid(source: dict):
try:
id_ = source["id"]
except KeyError:
raise IdNotFoundError()
if not isinstance(id_, str) and not id_.isnumeric():
raise InvalidIdError()
def get_name_key(source: dict):
check_id_valid(source)
try:
name = source["name"]
except KeyError:
raise NameNotFoundError()
If KeyError
had been handled in check_id_valid
in the first place, we would not have encountered this issue. However, code isn’t always as simple as the above example and the try...except
statement should both scope the exceptions and the code it contains to specifically what it can handle and not let other errors that are not properly handled slip into the except
flow. This will avoid accidentally handling errors that the except
block is not designed to handle.