How to determine if an exception was raised once you're in the finally block?

后端 未结 6 1749
一整个雨季
一整个雨季 2020-12-14 06:06

Is it possible to tell if there was an exception once you\'re in the finally clause? Something like:

try:
    funky code
finally:
    if ???:
          


        
相关标签:
6条回答
  • 2020-12-14 06:48

    If it was me, I'd do a little re-ordering of your code.

    raised = False
    try:
        # funky code
    except HandleThis:
        # handle it
        raised = True
    except Exception as ex:
        # Don't Handle This 
        raise ex
    finally:
        if raised:
            logger.info('funky code was raised')
    

    I've placed the raised boolean assignment outside of the try statement to ensure scope and made the final except statement a general exception handler for exceptions that you don't want to handle.

    This style determines if your code failed. Another approach might me to determine when your code succeeds.

    success = False
    try:
        # funky code
        success = True
    except HandleThis:
        # handle it
        pass
    except Exception as ex:
        # Don't Handle This 
        raise ex
    finally:
        if success:
            logger.info('funky code was successful')
        else:
            logger.info('funky code was raised')
    
    0 讨论(0)
  • 2020-12-14 06:50
    raised = True
    try:
        funky code
        raised = False
    except HandleThis:
        # handle it
    finally:
        logger.info('funky code raised %s', raised)
    

    Given the additional background information added to the question about selecting a log level, this seems very easily adapted to the intended use-case:

    mylog = WARNING
    try:
        funky code
        mylog = DEBUG
    except HandleThis:
        # handle it
    finally:
        mylog(...)
    
    0 讨论(0)
  • 2020-12-14 06:54

    If exception happened --> Put this logic in the exception block(s).
    If exception did not happen --> Put this logic in the try block after the point in code where the exception can occur.

    Finally blocks should be reserved for "cleanup actions," according to the Python language reference. When finally is specified the interpreter proceeds in the except case as follows: Exception is saved, then the finally block is executed first, then lastly the Exception is raised.

    0 讨论(0)
  • 2020-12-14 06:55

    Using a contextmanager

    You could use a custom contextmanager, for example:

    class DidWeRaise:
        __slots__ = ('exception_happened', )  # instances will take less memory
    
        def __enter__(self):
            return self
    
        def __exit__(self, exc_type, exc_val, exc_tb):
            # If no exception happened the `exc_type` is None
            self.exception_happened = exc_type is not None
    

    And then use that inside the try:

    try:
        with DidWeRaise() as error_state:
            # funky code
    finally:
        if error_state.exception_happened:
            print('the funky code raised')
    

    It's still an additional variable but it's probably a lot easier to reuse if you want to use it in multiple places. And you don't need to toggle it yourself.

    Using a variable

    In case you don't want the contextmanager I would reverse the logic of the trigger and toggle it only in case no exception has happened. That way you don't need an except case for exceptions that you don't want to handle. The most appropriate place would be the else clause that is entered in case the try didn't threw an exception:

    exception_happened = True
    try:
        # funky code
    except HandleThis:
        # handle this kind of exception
    else:
        exception_happened = False
    finally:
        if exception_happened:
            print('the funky code raised')
    

    And as already pointed out instead of having a "toggle" variable you could replace it (in this case) with the desired logging function:

    mylog = mylogger.WARNING
    try:
        with LogCapture() as log:
            funky_code()
    except HandleThis:
        # handle this kind of exception
    else:
        # In case absolutely no exception was thrown in the try we can log on debug level
        mylog = mylogger.DEBUG
    finally:
        for record in log.captured:
            mylog(record.msg, record.args)
    

    Of course it would also work if you put it at the end of your try (as other answers here suggested) but I prefer the else clause because it has more meaning ("that code is meant to be executed only if there was no exception in the try block") and may be easier to maintain in the long run. Although it's still more to maintain than the context manager because the variable is set and toggled in different places.

    Using sys.exc_info (works only for unhandled exceptions)

    The last approach I want to mention is probably not useful for you but maybe useful for future readers who only want to know if there's an unhandled exception (an exception that was not caught in any except block or has been raised inside an except block). In that case you can use sys.exc_info:

    import sys
    
    try:
        # funky code
    except HandleThis:
        pass
    finally:
        if sys.exc_info()[0] is not None:
            # only entered if there's an *unhandled* exception, e.g. NOT a HandleThis exception
            print('funky code raised')
    
    0 讨论(0)
  • 2020-12-14 07:04

    You can easily assign your caught exception to a variable and use it in the finally block, eg:

    >>> x = 1
    >>> error = None
    >>> try:
    ...     x.foo()
    ... except Exception as e:
    ...     error = e
    ... finally:
    ...     if error is not None:
    ...             print(error)
    ...
    'int' object has no attribute 'foo'
    
    0 讨论(0)
  • 2020-12-14 07:05

    Okay, so what it sounds like you actually just want to either modify your existing context manager, or use a similar approach: logbook actually has something called a FingersCrossedHandler that would do exactly what you want. But you could do it yourself, like:

    @contextmanager
    def LogCapture():
        # your existing buffer code here
        level = logging.WARN
        try:
            yield
        except UselessException:
            level = logging.DEBUG
            raise  # Or don't, if you just want it to go away
        finally:
            # emit logs here
    

    Original Response

    You're thinking about this a bit sideways.

    You do intend to handle the exception - you're handling it by setting a flag. Maybe you don't care about anything else (which seems like a bad idea), but if you care about doing something when an exception is raised, then you want to be explicit about it.

    The fact that you're setting a variable, but you want the exception to continue on means that what you really want is to raise your own specific exception, from the exception that was raised:

    class MyPkgException(Exception):  pass
    class MyError(PyPkgException): pass  # If there's another exception type, you can also inherit from that
    
    def do_the_badness():
        try:
            raise FileNotFoundError('Or some other code that raises an error')
        except FileNotFoundError as e:
            raise MyError('File was not found, doh!') from e
        finally:
            do_some_cleanup()
    
    try:
        do_the_badness()
    except MyError as e:
        print('The error? Yeah, it happened')
    

    This solves:

    • Explicitly handling the exception(s) that you're looking to handle
    • Making the stack traces and original exceptions available
    • Allowing your code that's going to handle the original exception somewhere else to handle your exception that's thrown
    • Allowing some top-level exception handling code to just catch MyPkgException to catch all of your exceptions so it can log something and exit with a nice status instead of an ugly stack trace
    0 讨论(0)
提交回复
热议问题