id01
id01

Reputation: 1587

Neater way to catch exception in exception handler in Python

In a Python program, one generally catches an exception using a try-except block:

try:
    # Do stuff
except ValueError:
    # Handle exception

The best way I know of to catch an exception in an exception handler is a nested try-except block. However, with many nested try-catch blocks, this may get a bit messy:

try:
    # Some assignment that throws an exception if the object is not in the list
    try:
        # Some assignment function that throws an exception if the the object is not already in the database
        # Error Handling
    except ValueError:
        try:
            # Some function that throws an exception if the object does not have an undesired property
            # Error Handling
        except AttributeError:
            try:
                # Some function that throws an exception if an error happens
            except Exception:
                # Exception handling
except ValueError:
    # Exception handling

Is there a neater way to do this? Something like:

try:
   # Some assignment that throws an exception if the object is not in the list
   try:
       # Some assignment function that throws an exception if the object is not already in the database
   except ValueError:
       # Some function that throws an exception if the object does not have an undesired property
   exceptexcept AttributeError:
       # Some function that throws an exception if an error happens
   exceptexcept Exception:
       # Exception handling 
except ValueError:
   # Exception handling

Upvotes: 2

Views: 5411

Answers (1)

Matthias Fripp
Matthias Fripp

Reputation: 18625

This sounds like a loop where you want to keep trying until you succeed or run out of options. So you could implement it that way, e.g., something like this

# Each pair contains a function to call and an exception that can be caught.
# If that exception is raised, the next function will be tried.
action_list = [
    (get_from_list, ValueError),  # throws ValueError if item can't be retrieved
    (get_from_database, ValueError),  # throws ValueError if item can't be retrieved
    (get_from_object, AttributeError),  # throws AttributeError if item lacks desired property
]

result = None
for action, ex in action_list:
    try:
        result = action(key)
        break
    except ex:
        continue

You could tidy this up a bit by having all your helper functions raise a custom exception like "NotFound", which you then use as a signal to check the next level, like this:

# actions to try; all raise NotFound if unsuccessful
action_list = [
    get_from_list, get_from_database, get_from_object
]

result = None
for action in action_list:
    try:
        result = action(key)
        break
    except NotFound:
        continue

Or you could put all the steps in a function that returns as soon as it succeeds. This way your assignments could be done in regular code rather than using helper functions:

def get_value(key):

    try:
        return some_list[int(key)]
    except ValueError:
        pass

    try:
        return get_from_database(key)
    except ValueError:
        pass

    try:
        return getattr(some_object, key)
    except AttributeError:
        pass

    return None

If you don't want another function you could abuse a for loop:

result = None
for _ in range(1):

    try:
        result = some_list[int(key)]
        break
    except ValueError:
        pass

    try:
        result = get_from_database(key)
        break
    except ValueError:
        pass

    try:
        result = getattr(some_object, key)
        break
    except AttributeError:
        pass

Or you could use a single outer try/except with a custom Found exception as a "structured goto":

result = None
try:
    try:
        result = some_list[int(key)]
        raise Found
    except ValueError:
        pass
    try:
        result = get_from_database(key)
        raise Found
    except ValueError:
        pass
    try:
        result = getattr(some_object, key)
        raise Found
    except AttributeError:
        pass
except Found:
    pass  # all good

Or there's this tried-but-true construct:

result = None
if result is None:
    try:
        result = some_list[int(key)]
    except ValueError:
        pass
if result is None:
    try:
        result = get_from_database(key)
    except ValueError:
        pass
if result is None:
    try:
        result = getattr(some_object, key)
    except AttributeError:
        pass

Upvotes: 5

Related Questions