Reputation: 87
Is there a functional difference between the exception handling in code blocks 1,2,3? I want to print different messages based on the type error for example including the error code if psycopg2 error. I have read that nested try except blocks are good practice. I'm using psycopg2 as an example.
# code block 1
def my_func(fun_arg):
try:
# ... some stuff
except psycopg2.Error as error:
print(f"Error while inserting data to PostgreSQL {type(error).__name__} {error.pgcode} {error.pgerror}")
except Exception as error:
print(f'error in my_func {type(error).__name__} {error.args}')
# code block 2
def my_func(fun_arg):
try:
# ... some stuff
try:
# ... some stuff
except psycopg2.Error as error:
print(f"Error while inserting data to PostgreSQL {type(error).__name__} {error.pgcode} {error.pgerror}")
except Exception as error:
print(f'error in my_func {type(error).__name__} {error.args}')
# code block 3
def my_func(fun_arg):
try:
# ... some stuff
except (psycopg2.Error, Exception) as error:
if (type(error).__name__ in (
'DatabaseError', 'OperationalError', 'NotSupportedError',
'ProgrammingError', 'DataError','IntegrityError',))
print(f"Error while inserting data to PostgreSQL {type(error).__name__} {error.pgcode} {error.pgerror}")
else:
print(f'error in my_func {type(error).__name__} {error.args}')
Upvotes: 0
Views: 99
Reputation: 417
In your case, there is no functional difference. In such a case, you may follow the Zen of Python to choose the one that fits best. There are a lot of ways to interpret it but I would choose block number 1 as it is the simplest, the flattest, and the most readable one.
Block number one is the preferred one for the reasons above I'd say.
Block number two is needed when all the branches of the inner try
can raise the same exception and the handler of the exception is also the same. E.g.:
try:
try:
store_to_db(value)
except ValueError:
store_to_db(fallback)
except DbError:
log_error()
Block number three is just a more complicated and more difficult-to-read alternative to block number one. I cannot imagine a practical use case for it except that there is a shared code for both exceptions. E.g.:
try:
do_something()
except (DatabaseError, OperationalError, KeyError) as error:
data = collect_some_data()
if type(error).__name__ in ('DatabaseError', 'OperationalError'):
print(f"Error while inserting data to PostgreSQL {data}")
else:
print(f'error in my_func {data}')
Although isinstance
is probably preferred over type(error).__name__ ==
. And I would still argue that it is better to split it into multiple except
clauses and repeat the call to collect_some_data
but it is a matter of personal preferences.
Another thing to take into account is that it is generally preferred to "limit the try
clause to the absolute minimum amount of code necessary as it avoids masking bugs" [PEP 8]. This is another reason to avoid nested try
whenever possible.
Upvotes: 1
Reputation: 820
I think either of the first two options are fine.
Personally I find I think the first is easier to read for a short simple function although if my_func
was larger and more complex with nesting then I would opt for the second option as it makes it clear where exactly where sycopg2.Error
may be raised.
The wouldn't use the third option. If you want to catch multiple exceptions use this syntax:
except (RuntimeError, TypeError, NameError):
pass
Upvotes: 0