4

I have a trouble to find the "pythonic" way to do this: I need to catch different blocks of code with the same try-except pattern. The blocks to catch are different from each other. Currently I am repeating the same try-except pattern in several points of the code with a long list of exceptions.

try:
    block to catch
except E1, e:
    log and do something
except E2, e:
    log and do something
...
except Exception, e:
    log and do something

There is a good way to solve this using the with statement and a context manager decorator:

from contextlib import contextmanager

@contextmanager
def error_handler():
    try:
        yield
    except E1, e:
        log and do something
    ...
    except Exception, e:
        log and do something


...
with error_handler():
    block to catch
...

But, what happens if I need to know whether or not there has been an exception in the block? i.e. Is there any alternative to do something like the previous with block for try-except-else?

A use case example:

for var in vars:
    try:
        block to catch
    except E1, e:
        log and do something
        # continue to the next loop iteration
    except E2, e:
        log and do something
        # continue to the next loop iteration
    ...
    except Exception, e:
        log and do something
        # continue to the next loop iteration
    else:
        do something else

Could I do something like that in a pythonic way to avoid repeat the same try-except pattern again and again?

Paul Rigor
  • 986
  • 1
  • 12
  • 23
Garet
  • 365
  • 2
  • 13

3 Answers3

1

Can't see a way to give the no exception information back to the caller. You could only put the error handling in a separate function

def error_handler(exception):
    if isinstance(exception, E1):
        log and do something
    elif isinstance(exception, E2):
    ...
    else:
        log and do something

try:
    block to catch
except Exception, e:
    error_handler(e)
else:
    do something else
Daniel
  • 42,087
  • 4
  • 55
  • 81
  • Good idea! It had not occurred to me separate the error handling in a external function – Garet May 01 '14 at 09:51
  • Helpful, however Python's motto is "explicit is better than implicit", keep that in mind when using "detached" error handlers. Moreover, such error handler cannot affect locals of given function/block. I'd need a very good reason (a lot of saved repetition) to accept this style. – Dima Tisnek May 01 '14 at 10:55
1

I can see that you already got an answer, but building on what you already have, you could yield back an object indicating the error status, and use that for checking in your loop.

Before using this style though, you should really consider if hiding the error handling/logging in this kind of structure is really something you want to do, being "Pythonic" is usually leaning more towards being explicit instead of hiding the details.

from contextlib import contextmanager

@contextmanager
def error_handler():
    error = True
    try:
        class Internal:
            def caughtError(self): return error
        yield Internal()
    except Exception as e:
        print("Logging#1")
    except BaseException as e:
        print("Logging#2")
    else:
        error = False

with error_handler() as e:
    print("here")
#    raise Exception("hopp")

print(e.caughtError())         # True if an error was logged, False otherwise
Joachim Isaksson
  • 176,943
  • 25
  • 281
  • 294
  • 1
    Thank you!!! This is exactly what I was looking. I know that hiding the error handling is a bad practice in python. But I need repeat a long try-except block (about 40 lines) in about of 30 points of the project. For reasons of maintainability I need not repeat this block again and again. – Garet May 02 '14 at 08:22
  • Would say, using a variable introduced with `with` after the with-block is bad habbit. It's like using a for-variable outside for. For nicer interface you could use `__nonzero__` instead of `caughtError`, which btw is not pep8 conform. – Daniel May 02 '14 at 11:37
0

Fundamentally you want to let your error propagate, so instead of, say:

def foo():
    try:
        something = xxx
    except ValueError as e:
        logging.warn("oh noes %s", e)
        return 42
    except IndexError as e:
        logging.warn("on no, not again %s", e)
        return 43

    go.on(something=something)

How about this:

def foo():
    try:
        something = xxx
    except Exception:
        logging.exception("oh noes")
        return

    go.on(something=something)

And then perhaps simplify further with:

def foo():
    go.on(something=xxx)

And let caller handle the error.

This approach is typically called Crash Early

Dima Tisnek
  • 11,241
  • 4
  • 68
  • 120