python how to re-raise an exception which is already caught?

前端 未结 3 1055
南笙
南笙 2021-01-21 07:50
import sys
def worker(a):
    try:
        return 1 / a
    except ZeroDivisionError:
        return None


def master():
    res = worker(0)
    if not res:
        pri         


        
相关标签:
3条回答
  • 2021-01-21 08:06

    In your case, the exception in worker returns None. Once that happens, there's no getting the exception back. If your master function knows what the return values should be for each function (for example, ZeroDivisionError in worker reutrns None, you can manually reraise an exception.

    If you're not able to edit the worker functions themselves, I don't think there's too much you can do. You might be able to use some of the solutions from this answer, if they work in code as well as on the console.

    krflol's code above is kind of like how C handled exceptions - there was a global variable that, whenever an exception happened, was assigned a number which could later be cross-referenced to figure out what the exception was. That is also a possible solution.

    If you're willing to edit the worker functions, though, then escalating an exception to the code that called the function is actually really simple:

    try: 
        # some code
    except:
        # some response
        raise
    

    If you use a blank raise at the end of a catch block, it'll reraise the same exception it just caught. Alternatively, you can name the exception if you need to debug print, and do the same thing, or even raise a different exception.

    except Exception as e:
        # some code
        raise e
    
    0 讨论(0)
  • 2021-01-21 08:25

    What you're trying to do won't work. Once you handle an exception (without re-raising it), the exception, and the accompanying state, is cleared, so there's no way to access it. If you want the exception to stay alive, you have to either not handle it, or keep it alive manually.

    This isn't that easy to find in the docs (the underlying implementation details about CPython are a bit easier, but ideally we want to know what Python the language defines), but it's there, buried in the except reference:

    … This means the exception must be assigned to a different name to be able to refer to it after the except clause. Exceptions are cleared because with the traceback attached to them, they form a reference cycle with the stack frame, keeping all locals in that frame alive until the next garbage collection occurs.

    Before an except clause’s suite is executed, details about the exception are stored in the sys module and can be accessed via sys.exc_info(). sys.exc_info() returns a 3-tuple consisting of the exception class, the exception instance and a traceback object (see section The standard type hierarchy) identifying the point in the program where the exception occurred. sys.exc_info() values are restored to their previous values (before the call) when returning from a function that handled an exception.

    Also, this is really the point of exception handlers: when a function handles an exception, to the world outside that function, it looks like no exception happened. This is even more important in Python than in many other languages, because Python uses exceptions so promiscuously—every for loop, every hasattr call, etc. is raising and handling an exception, and you don't want to see them.


    So, the simplest way to do this is to just change the workers to not handle the exceptions (or to log and then re-raise them, or whatever), and let exception handling work the way it's meant to.

    There are a few cases where you can't do this. For example, if your actual code is running the workers in background threads, the caller won't see the exception. In that case, you need to pass it back manually. For a simple example, let's change the API of your worker functions to return a value and an exception:

    def worker(a):
        try:
            return 1 / a, None
        except ZeroDivisionError as e:
            return None, e
    
    def master():
        res, e = worker(0)
        if e:
            print(e)
            raise e
    

    Obviously you can extend this farther to return the whole exc_info triple, or whatever else you want; I'm just keeping this as simple as possible for the example.

    If you look inside the covers of things like concurrent.futures, this is how they handle passing exceptions from tasks running on a thread or process pool back to the parent (e.g., when you wait on a Future).


    If you can't modify the workers, you're basically out of luck. Sure, you could write some horrible code to patch the workers at runtime (by using inspect to get their source and then using ast to parse, transform, and re-compile it, or by diving right down into the bytecode), but this is almost never going to be a good idea for any kind of production code.

    0 讨论(0)
  • 2021-01-21 08:25

    Not tested, but I suspect you could do something like this. Depending on the scope of the variable you'd have to change it, but I think you'll get the idea

    try:
        something
    except Exception as e:
        variable_to_make_exception = e
    

    .....later on use variable

    an example of using this way of handling errors:

    errors = {}
    try:
        print(foo)
    except Exception as e:
        errors['foo'] = e
    try:
        print(bar)
    except Exception as e:
        errors['bar'] = e
    
    
    print(errors)
    raise errors['foo']
    

    output..

    {'foo': NameError("name 'foo' is not defined",), 'bar': NameError("name 'bar' is not defined",)}
    Traceback (most recent call last):
      File "<input>", line 13, in <module>
      File "<input>", line 3, in <module>
    NameError: name 'foo' is not defined
    
    0 讨论(0)
提交回复
热议问题