d3f2
d3f2

Reputation: 87

Functional difference in exception handling between consecutive except, nested try, tuple exception with if else code blocks

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

Answers (2)

radekholy24
radekholy24

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

Jacob
Jacob

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

Related Questions