🎓 A1Learner
🔍
0 of 102 lessons completed

Python Exception Chaining

Exception chaining links a new exception to the older exception that caused it. raise NewError from old_error names the cause on purpose.

Name the cause with from

The word from names the cause. Python saves that error as __cause__.

try:
    try:
        int("abc")
    except ValueError as err:
        raise ValueError("Bad age text") from err
except ValueError as err:
    print(err)
    print(type(err.__cause__).__name__)

The quiet automatic link

Raising inside an except block links the old error too. That automatic link is the __context__.

try:
    try:
        int("abc")
    except ValueError:
        raise RuntimeError("Setup failed")
except RuntimeError as err:
    print(err)
    print(type(err.__context__).__name__)
    print(err.__cause__)

Hide the old error on purpose

The words from None hide the older error. Python also sets __suppress_context__ to True.

try:
    try:
        int("abc")
    except ValueError:
        raise ValueError("Bad code") from None
except ValueError as err:
    print(err)
    print(err.__cause__)
    print(err.__suppress_context__)

Attach a note

add_note() attaches one line of context. Python saves the notes in the __notes__ list.

try:
    raise ValueError("Bad port")
except ValueError as err:
    err.add_note("Port came from the config file")
    print(err.__notes__[0])

Example

def load_port(text):
    try:
        return int(text)
    except ValueError as err:
        new_error = ValueError("Port must be a whole number")
        new_error.add_note("Port came from settings text")
        raise new_error from err

try:
    load_port("eight")
except ValueError as err:
    print(type(err).__name__)
    print(err)
    print(type(err.__cause__).__name__)
    print(err.__cause__)
    print(err.__notes__[0])

Key points

  • Chaining: linking a new exception to the older exception that caused it
  • Use raise NewError from old_error to name the cause on purpose
  • __cause__: the exception named after from in a raise line
  • __context__: the exception being handled when a new one was raised without from
  • Use raise NewError from None to hide the older exception on purpose
  • Use add_note() to attach a short context line to an exception (Python 3.11+)

In this example, load_port tries to turn text into a number. The text eight fails, so int() raises a ValueError. The except block builds a friendlier ValueError for the caller. It adds a note saying where the port text came from. The raise line names the first error as the cause. The outer except then prints the new error first. Next it prints the cause, which is the raw parse error. Last it prints the note stored on the new error.

Note

In a chained traceback, Python prints the cause first and then the new exception.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Python Playground

Exercise

What does raise ConfigError("Bad file") from err do?