2

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
id01
  • 1,491
  • 1
  • 14
  • 21
  • In your last block, what is your intent with `exceptexcept`? If these were simply `except`, then your code would work, likely as you expect. (With the caveat that the outer `ValueError` would only catch exceptions that occurred *outside* the inner try block (as those would be caught by, at least, the last `except Exception` block). – jedwards Nov 10 '17 at 05:17
  • I'm trying to catch an exception in the except clause, not in the try. – id01 Nov 10 '17 at 05:18
  • 2
    Assuming you are trying to catch errors in the _error handling code_, then there is nothing to be done. You'll have to work with this nested hierarchy. – cs95 Nov 10 '17 at 05:19
  • Sorry for the misunderstanding, in the case you describe I agree with coldspeed. – jedwards Nov 10 '17 at 05:20
  • 1
    Like they said, you're stuck with the nesting. So the moral of the story is: try to avoid using code in `except` blocks that can raise exceptions. ;) – PM 2Ring Nov 10 '17 at 05:21
  • @cᴏʟᴅsᴘᴇᴇᴅ thanks. I've noticed that this type of Python code is a bit more messy than it should be, but looks like I'll just have to live with it. – id01 Nov 10 '17 at 05:21
  • Recursive calls to the function with the try except in it? Like if you catch a value error, try to fix it in the exception, then call the same function again? – Jack Homan Nov 10 '17 at 05:28
  • @JackHoman Sure, that would work, but in practice it's rare where you couldn't do the same thing with a simple `while` loop rather than recursion. Another option is to simply let the exceptions bubble up to the calling code, where you have a chain of (un-nested) `except` clauses, assuming that the calling code is able to handle those exceptions. – PM 2Ring Nov 10 '17 at 05:34
  • For the first *two*; `object is not in the list` and `is not already in the database` - can you discern which is which from the accompanying error message? – wwii Nov 10 '17 at 05:41

1 Answers1

5

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
Matthias Fripp
  • 17,670
  • 5
  • 28
  • 45