Home > Enterprise >  How to avoid duplicates when python multiple except executes the same method?
How to avoid duplicates when python multiple except executes the same method?

Time:06-16

I have the code block like below:

try:
   method()
except ErrorType1:
   todo()
   return
except ErrorType2 as e:
   todo()
   raise e

Basically for the two error types, I need to execute todo() first, then either return or raise e. Is it possible to just write todo() once? I was thinking using finally but don't think that actually works.

CodePudding user response:

You could catch both exceptions in one except clause, execute todo and then decide to do based on the exception type:

try:
   method()
except (ErrorType1, ErrorType2) as e:
   todo()
   if isinstance(e, ErrorType1):
       return
   raise

Note - as pointed out by @ShadowRanger in the comments to the question - you should just use raise to re-raise the existing exception, using raise e will raise a second copy of it, resulting in the traceback including the line with raise e on it as well as the line where the original error occurred.

CodePudding user response:

If you have an common set of instructions (either encapsulated as a function or series of functions) that must be executed as part of an exception handling, consider using a context manager to encapsulate the common bits. The following two results in identical outcome, albeit with different construction (one using try..finally, the other using try..except).

from contextlib import contextmanager
@contextmanager
def context1(method):
    print("starting context1")
    completed = False
    try:
        yield method()
        completed = True
    finally:
        if completed:
            commit()
        else:
            rollback()
    print("finished")
@contextmanager
def context2(method):
    print("starting context2")
    try:
        yield method()
    except Exception:
        rollback()
        raise
    else:
        commit()
    print("finished")

The latter one will not be able to deal with KeyboardInterrupt or other exceptions that subclass off BaseException, so for certain use case this is not exactly ideal, though it is included to follow suite of the question. The first one is more of a response to the fact that you never appeared to have tried using finally, but rather simply thinking it does not actually works, and thus provided to show it can be used to achieve your goal (where only todo() in the question is executed if failure, through the use of a boolean variable).

In both cases, note how the common control flow is fully encapsulated inside the context manager, and usage is fairly straightforward like so such that all the unique extra cases can be done with another try..except block around the with context block.

try:
    with context1(f) as result:
        pass  # or use the result to do something
except Exception: ...
    # all the special unique cases be handled here.

To complete demo, more code is below; the commit and rollback functions I defined the following:

def commit():
    print("commit")

def rollback():
    print("rollback")

Now to test it, I defined the following helpers:

from functools import partial

class ErrorType1(Exception):
    pass

class ErrorType2(Exception):
    pass

def raise_e(e):
    raise e

subject = [
    object,
    partial(raise_e, ErrorType1),
    partial(raise_e, ErrorType2),
]

With the tests defined as such (replace context1 with context2 for the other demonstration):

for f in subject:
    try:
        with context1(f) as result:
            print('done - got result %r' % result)
    except ErrorType2:
        print("ErrorType2 will be raised")
        # raise  # uncomment to actually re-raise the exception
    except Exception as e:
        print("Exception trapped: %r raised by %r" % (e, f))

Note the output of both the above should look about like so (aside from context1 vs context2):

starting context1
done - got result <object object at 0x7f20ccd3e180>
commit
finished
starting context1
rollback
Exception trapped: ErrorType1() raised by functools.partial(<function raise_e at 0x7f20ccb30af0>, <class '__main__.ErrorType1'>)
starting context1
rollback
ErrorType2 will be raised
  • Related