Ah! Ok, now this is an interesting question!
Here is is the same approximate function, but grabbing the exception directly from sys.exc_info()
:
import sys
import traceback
def save_if_allowed(fn):
def wrapped(*args, **kwargs):
try:
return fn(*args, **kwargs) if enabled else None
except Exception:
print "The exception:"
print "".join(traceback.format_exception(*sys.exc_info()))
return None
return wrapped
@save_if_allowed
def stuff():
raise Exception("stuff")
def foo():
stuff()
foo()
And it's true: no higher stack frames are included in the traceback that's printed:
$ python test.py
The exception:
Traceback (most recent call last):
File "x.py", line 21, in wrapped
return fn(*args, **kwargs) if enabled else None
File "x.py", line 29, in stuff
raise Exception("stuff")
Exception: stuff
Now, to narrow this down a bit, I suspect it's happening because the stack frame only includes stack information up until the most recent try/except
block… So we should be able to recreate this without the decorator:
$ cat test.py
def inner():
raise Exception("inner")
def outer():
try:
inner()
except Exception:
print "".join(traceback.format_exception(*sys.exc_info()))
def caller():
outer()
caller()
$ python test.py
Traceback (most recent call last):
File "x.py", line 42, in outer
inner()
File "x.py", line 38, in inner
raise Exception("inner")
Exception: inner
Ah ha! Now, on reflection, this does make sense in a certain kind of way: at this point, the exception has only encountered two stack frames: that of inner()
and that of outer()
— the exception doesn't yet know from whence outer()
was called.
So, to get the complete stack, you'll need to combine the current stack with the exception's stack:
$ cat test.py
def inner():
raise Exception("inner")
def outer():
try:
inner()
except Exception:
exc_info = sys.exc_info()
stack = traceback.extract_stack()
tb = traceback.extract_tb(exc_info[2])
full_tb = stack[:-1] + tb
exc_line = traceback.format_exception_only(*exc_info[:2])
print "Traceback (most recent call last):"
print "".join(traceback.format_list(full_tb)),
print "".join(exc_line)
def caller():
outer()
caller()
$ python test.py
Traceback (most recent call last):
File "test.py", line 56, in <module>
caller()
File "test.py", line 54, in caller
outer()
File "test.py", line 42, in outer
inner()
File "test.py", line 38, in inner
raise Exception("inner")
Exception: inner
See also:
ValueError
within a function that I decorate withsave_if_allowed
... What are you doing that allows the function to raise aSyntaxError
when it is run? (as opposed to when defined). – TrilobateSyntaxError
s are raised at parse time, not run time, so the decorator should (normally) never see aSyntaxError
. – Passwordeval
/exec
, but ... – Trilobate.py
extension suggests otherwise... – Hypostatize0=1
to produce an exception as an example. The focus is on the traceback which for me is cut off as posted above @Hypostatize @Gibberish @DavidWolever @Trilobate – Cave0=1
is a SyntaxError, and if you have a syntax error, it will be the only line in the traceback. There is no way to have the output you had with such an exception (and all others give the full traceback as far as I can tell). – Hypostatize