Raise is re-raising the last exception you caught, not the last exception you raised
(reposted from comments for clarity)
Explain the term “raise an exception” without using the terms “raise” or “throw”
`raise` as a function - Ideas - Discussions on Python.org
exception - What does raise in Python raise? - Stack Overflow
How to use "raise" keyword in Python - Stack Overflow
Videos
I’m a python learner and am pretty ok with the syntax, and am now finding myself struggling with some of the deeper concepts, in this case error handling. Our environment is python running in a pytest framework using some pre-structured functions along with scripts and functions we write ourselves. I have visibility into the scripts and functions I write, along with some of the pre-structured functions, but the lowest level functions are beyond my view and control. I regularly run into exceptions in these lower levels and am trying to understand what’s going on. All the references I find say something like “raising an exception is when an executions error occurs, the error is raised as an exception”. This makes me nuts as they are explaining the term by using the term. So… Q1: what happens when an “exception is raised”? In terms of program execution and control and variables. Q2: if my script calls a function1 that calls a function2 which contains a command that “raises an exception” can I catch and handle it in function1 or in my top-level script, and if so, how? Q3: if I’m asking the wrong question, what should I be asking and/or researching?
Raise is re-raising the last exception you caught, not the last exception you raised
(reposted from comments for clarity)
On python2.6
I guess, you are expecting the finally block to be tied with the "try" block where you raise the exception "B". The finally block is attached to the first "try" block.
If you added an except block in the inner try block, then the finally block will raise exception B.
try:
raise Exception("a")
except:
try:
raise Exception("b")
except:
pass
finally:
raise
Output:
Traceback (most recent call last):
File "test.py", line 5, in <module>
raise Exception("b")
Exception: b
Another variation that explains whats happening here
try:
raise Exception("a")
except:
try:
raise Exception("b")
except:
raise
Output:
Traceback (most recent call last):
File "test.py", line 7, in <module>
raise Exception("b")
Exception: b
If you see here, replacing the finally block with except does raise the exception B.
It has two purposes.
jackcogdill has given the first one:
It's used for raising your own errors.
if something: raise Exception('My error!')
The second is to reraise the current exception in an exception handler, so that it can be handled further up the call stack.
try:
generate_exception()
except SomeException as e:
if not can_handle(e):
raise
handle_exception(e)
raise without any arguments is a special use of python syntax. It means get the exception and re-raise it. If this usage it could have been called reraise.
raise
From The Python Language Reference:
If no expressions are present, raise re-raises the last exception that was active in the current scope.
If raise is used alone without any argument is strictly used for reraise-ing. If done in the situation that is not at a reraise of another exception, the following error is shown:
RuntimeError: No active exception to reraise
The difference is that when you use from, the __cause__ attribute is set and the message states that the exception was directly caused by. If you omit the from then no __cause__ is set, but the __context__ attribute may be set as well, and the traceback then shows the context as during handling something else happened.
Setting the __context__ happens if you used raise in an exception handler; if you used raise anywhere else no __context__ is set either.
If a __cause__ is set, a __suppress_context__ = True flag is also set on the exception; when __suppress_context__ is set to True, the __context__ is ignored when printing a traceback.
When raising from a exception handler where you don't want to show the context (don't want a during handling another exception happened message), then use raise ... from None to set __suppress_context__ to True.
In other words, Python sets a context on exceptions so you can introspect where an exception was raised, letting you see if another exception was replaced by it. You can also add a cause to an exception, making the traceback explicit about the other exception (use different wording), and the context is ignored (but can still be introspected when debugging). Using raise ... from None lets you suppress the context being printed.
See the raise statement documenation:
The
fromclause is used for exception chaining: if given, the second expression must be another exception class or instance, which will then be attached to the raised exception as the__cause__attribute (which is writable). If the raised exception is not handled, both exceptions will be printed:>>> try: ... print(1 / 0) ... except Exception as exc: ... raise RuntimeError("Something bad happened") from exc ... Traceback (most recent call last): File "<stdin>", line 2, in <module> ZeroDivisionError: int division or modulo by zero The above exception was the direct cause of the following exception: Traceback (most recent call last): File "<stdin>", line 4, in <module> RuntimeError: Something bad happenedA similar mechanism works implicitly if an exception is raised inside an exception handler or a
finallyclause: the previous exception is then attached as the new exception’s__context__attribute:>>> try: ... print(1 / 0) ... except: ... raise RuntimeError("Something bad happened") ... Traceback (most recent call last): File "<stdin>", line 2, in <module> ZeroDivisionError: int division or modulo by zero During handling of the above exception, another exception occurred: Traceback (most recent call last): File "<stdin>", line 4, in <module> RuntimeError: Something bad happened
Also see the Built-in Exceptions documentation for details on the context and cause information attached to exceptions.
In 2005, PEP 3134, Exception Chaining and Embedded Tracebacks introduced exception chaining:
- implicit chaining with explicit
raise EXCEPTIONor implicit raise (__context__attribute); - explicit chaining with explicit
raise EXCEPTION from CAUSE(__cause__attribute).
Motivation
During the handling of one exception (exception A), it is possible that another exception (exception B) may occur. In today’s Python (version 2.4), if this happens, exception B is propagated outward and exception A is lost. In order to debug the problem, it is useful to know about both exceptions. The
__context__attribute retains this information automatically.Sometimes it can be useful for an exception handler to intentionally re-raise an exception, either to provide extra information or to translate an exception to another type. The
__cause__attribute provides an explicit way to record the direct cause of an exception.[…]
Implicit Exception Chaining
Here is an example to illustrate the
__context__attribute:def compute(a, b): try: a/b except Exception, exc: log(exc) def log(exc): file = open('logfile.txt') # oops, forgot the 'w' print >>file, exc file.close()Calling
compute(0, 0)causes aZeroDivisionError. Thecompute()function catches this exception and callslog(exc), but thelog()function also raises an exception when it tries to write to a file that wasn’t opened for writing.In today’s Python, the caller of
compute()gets thrown anIOError. TheZeroDivisionErroris lost. With the proposed change, the instance ofIOErrorhas an additional__context__attribute that retains theZeroDivisionError.[…]
Explicit Exception Chaining
The
__cause__attribute on exception objects is always initialized toNone. It is set by a new form of theraisestatement:raise EXCEPTION from CAUSEwhich is equivalent to:
exc = EXCEPTION exc.__cause__ = CAUSE raise excIn the following example, a database provides implementations for a few different kinds of storage, with file storage as one kind. The database designer wants errors to propagate as
DatabaseErrorobjects so that the client doesn’t have to be aware of the storage-specific details, but doesn’t want to lose the underlying error information.class DatabaseError(Exception): pass class FileDatabase(Database): def __init__(self, filename): try: self.file = open(filename) except IOError, exc: raise DatabaseError('failed to open') from excIf the call to
open()raises an exception, the problem will be reported as aDatabaseError, with a__cause__attribute that reveals theIOErroras the original cause.Enhanced Reporting
The default exception handler will be modified to report chained exceptions. The chain of exceptions is traversed by following the
__cause__and__context__attributes, with__cause__taking priority. In keeping with the chronological order of tracebacks, the most recently raised exception is displayed last; that is, the display begins with the description of the innermost exception and backs up the chain to the outermost exception. The tracebacks are formatted as usual, with one of the lines:The above exception was the direct cause of the following exception:
or
During handling of the above exception, another exception occurred:
between tracebacks, depending whether they are linked by
__cause__or__context__respectively. Here is a sketch of the procedure:def print_chain(exc): if exc.__cause__: print_chain(exc.__cause__) print '\nThe above exception was the direct cause...' elif exc.__context__: print_chain(exc.__context__) print '\nDuring handling of the above exception, ...' print_exc(exc)[…]
In 2012, PEP 415, Implement Context Suppression with Exception Attributes introduced exception context suppression with explicit raise EXCEPTION from None (__suppress_context__ attribute).
Proposal
A new attribute on
BaseException,__suppress_context__, will be introduced. Whenever__cause__is set,__suppress_context__will be set toTrue. In particular,raise exc from causesyntax will setexc.__suppress_context__toTrue. Exception printing code will check for that attribute to determine whether context and cause will be printed.__cause__will return to its original purpose and values.There is precedence for
__suppress_context__with theprint_line_and_fileexception attribute.To summarize,
raise exc from causewill be equivalent to:exc.__cause__ = cause raise excwhere
exc.__cause__ = causeimplicitly setsexc.__suppress_context__.
So in PEP 415, the sketch of the procedure given in PEP 3134 for the default exception handler (its job is to report exceptions) becomes the following:
def print_chain(exc):
if exc.__cause__:
print_chain(exc.__cause__)
print '\nThe above exception was the direct cause...'
elif exc.__context__ and not exc.__suppress_context__:
print_chain(exc.__context__)
print '\nDuring handling of the above exception, ...'
print_exc(exc)
As Russell said,
A bare
raisestatement re-raises the last caught exception.
It doesn't matter whether this is happening in a try-except block or not. If there has been a caught exception, then calling raise will re-raise that exception. Otherwise, Python will complain that the previously caught exception is None and raise a TypeError because None is not something that can actually be raised.
As tdelaney said, it doesn't seem to make sense to do this except in an error-handling function. Personally I'd say that it doesn't even belong in an error-handling function, as the raise should still be in the except clause. Someone could use this in an attempt to execute code whether or not an error occurs, but a finally clause is the proper way to do that. Another possibility would be using this as a way to determine if an error occurred while executing the function, but there are much better ways to do that (such as returning an extra value that indicates if/where an error occurred).
A bare raise statement re-raises the last caught exception. https://docs.python.org/2/tutorial/errors.html#raising-exceptions