How can I modify a Python traceback object when raising an exception?

You can remove the top of the traceback easily with by raising with the tb_next element of the traceback:

except:
    ei = sys.exc_info()
    raise ei[0], ei[1], ei[2].tb_next

tb_next is a read_only attribute, so I don't know of a way to remove stuff from the bottom. You might be able to screw with the properties mechanism to allow access to the property, but I don't know how to do that.


Take a look at what jinja2 does here:

https://github.com/mitsuhiko/jinja2/blob/5b498453b5898257b2287f14ef6c363799f1405a/jinja2/debug.py

It's ugly, but it seems to do what you need done. I won't copy-paste the example here because it's long.


Starting with Python 3.7, you can instantiate a new traceback object and use the .with_traceback() method when throwing. Here's some demo code using either sys._getframe(1) (or a more robust alternative) that raises an AssertionError while making your debugger believe the error occurred in myassert(False): sys._getframe(1) omits the top stack frame.

What I should add is that while this looks fine in the debugger, the console behavior unveils what this is really doing:

Traceback (most recent call last):
  File ".\test.py", line 35, in <module>
    myassert_false()
  File ".\test.py", line 31, in myassert_false
    myassert(False)
  File ".\test.py", line 26, in myassert
    raise AssertionError().with_traceback(back_tb)
  File ".\test.py", line 31, in myassert_false
    myassert(False)
AssertionError

Rather than removing the top of the stack, I have added a duplicate of the second-to-last frame.

Anyway, I focus on how the debugger behaves, and it seems this one works correctly:

"""Modify traceback on exception.

See also https://github.com/python/cpython/commit/e46a8a
"""

import sys
import types


def myassert(condition):
    """Throw AssertionError with modified traceback if condition is False."""
    if condition:
        return

    # This function ... is not guaranteed to exist in all implementations of Python.
    # https://docs.python.org/3/library/sys.html#sys._getframe
    # back_frame = sys._getframe(1)
    try:
        raise AssertionError
    except AssertionError:
        traceback = sys.exc_info()[2]
        back_frame = traceback.tb_frame.f_back

    back_tb = types.TracebackType(tb_next=None,
                                  tb_frame=back_frame,
                                  tb_lasti=back_frame.f_lasti,
                                  tb_lineno=back_frame.f_lineno)
    raise AssertionError().with_traceback(back_tb)


def myassert_false():
    """Test myassert(). Debugger should point at the next line."""
    myassert(False)


if __name__ == "__main__":
    myassert_false()

enter image description here