This nice little Python decorator can configurably disabled decorated functions:
enabled = get_bool_from_config()
def run_if_enabled(fn):
def wrapped(*args, **kwargs):
try:
return fn(*args, **kwargs) if enabled else None
except Exception:
log.exception('')
return None
return wrapped
alas, if an exception is raised within fn()
the traceback shows only up to the wrapper:
Traceback (most recent call last):
File "C:\my_proj\run.py", line 46, in wrapped
return fn(*args, **kwargs) if enabled else None
File "C:\my_proj\run.py", line 490, in a_decorated_function
some_dict['some_value']
KeyError: 'some_value'
A traceback is a report containing the function calls made in your code at a specific point. Tracebacks are known by many names, including stack trace, stack traceback, backtrace, and maybe others. In Python, the term used is traceback.
Some of the common traceback errors are:NameError. IndexError. KeyError. TypeError.
The lines starting with “Traceback (most recent call last):” are the traceback. It is the stack of your program at the time when your program encountered the exception. In the above example, the traceback is in the “most recent call last” order.
In computing, a stack trace (also called stack backtrace or stack traceback) is a report of the active stack frames at a certain point in time during the execution of a program.
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:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With