The documentation for the raise statement with no arguments says
If no expressions are present, raise re-raises the last exception that was active in the current scope.
I used to think that meant that the current function had to be executing an except
clause. After reading this question and experimenting a little, I think it means that any function on the stack has to be executing an except
clause, but I'm not sure. Also, I've realized I have no idea how the stack trace works with a no-arg raise:
def f():
try:
raise Exception
except:
g()
def g():
raise
f()
produces
Traceback (most recent call last):
File "foo", line 10, in <module>
f()
File "foo", line 5, in f
g()
File "foo", line 3, in f
raise Exception
Exception
That doesn't look like the stack at the time of the initial raise, or the stack at the time of the re-raise, or the concatenation of both stacks, or anything I can make sense of.
Am I right about a no-arg raise looking for any function on the stack executing an except
clause? Also, how does the stack trace work on a reraise?
When you
raise
without arguments, the interpreter looks for the last exception raised and handled. It then acts the same as if you usedraise
with the most recent exception type, value and traceback.This is stored in the interpreter state for the current thread, and the same information can be retrieved using
sys.exc_info()
. By 'handled' I mean that an except clause caught the exception. Quoting thetry
statement documentation:See the implemenation notes in the Python evaluation loop (C code), specifically:
The traceback reflects how you came to the re-raise accurately. It is the current stack (line 10 calling
f()
, line 5 callingg()
) plus the original location of the exception raised: line 3.It turns out Python uses a surprising way of building tracebacks. Rather than building the whole stack trace on exception creation (like Java) or when an exception is raised (like I used to think), Python builds up a partial traceback one frame at a time as the exception bubbles up.
Every time an exception bubbles up to a new stack frame, as well as when an exception is raised with the one-argument form of
raise
(or the two-argument form, on Python 2), the Python bytecode interpreter loop executesPyTraceback_Here
to add a new head to the linked list of traceback objects representing the stack trace. (0-argumentraise
, and 3-argumentraise
on Python 2, skip this step.)Python maintains a per-thread stack of exceptions (and tracebacks) suspended by
except
andfinally
blocks that haven't finished executing. 0-argumentraise
restores the exception (and traceback) represented by the top entry on this stack, even if theexcept
orfinally
is in a different function.When
f
executes itsraise
:Python builds a traceback corresponding to just that line:
When
g
executes 0-argumentraise
, this traceback is restored, but no entry is added for the 0-argumentraise
line.Afterwards, as the exception bubbles up through the rest of the stack, entries for the
g()
andf()
calls are added to the stack trace, resulting in the final stack trace that gets displayed: