PythonMastery

Add context to an error with add_note()

An error deep in a loop says what went wrong but not where in your data. exc.add_note() (3.11+) attaches the row or file, and the traceback shows it.

An error raised deep inside a loop tells you what failed, not which record caused it. "invalid literal for int(): 'thirty'" in a 50,000-row file is a search job. Since Python 3.11, exc.add_note() attaches a line of context to the exception and lets it carry on up, and the traceback prints the note under the error.

Before

python
rows = [["Ada", "34"], ["Linus", "thirty"], ["Grace", "41"]]

def load_ages(rows):
    ages = {}
    for name, age in rows:
        ages[name] = int(age)
    return ages

try:
    load_ages(rows)
except ValueError as exc:
    print(exc)
output
invalid literal for int() with base 10: 'thirty'

You know the value. You don't know the row, and on real data several rows might say "thirty".

After

Catch it where you still know the context, add a note, and re-raise with a bare raise so the original error and traceback survive.

python
rows = [["Ada", "34"], ["Linus", "thirty"], ["Grace", "41"]]

def load_ages(rows):
    ages = {}
    for line_no, (name, age) in enumerate(rows, start=1):
        try:
            ages[name] = int(age)
        except ValueError as exc:
            exc.add_note(f"row {line_no}: name={name!r}")
            raise
    return ages

try:
    load_ages(rows)
except ValueError as exc:
    print(type(exc).__name__, "-", exc)
    print(exc.__notes__)
output
ValueError - invalid literal for int() with base 10: 'thirty'
["row 2: name='Linus'"]

Here the notes are printed by hand to show them. Left uncaught, Python prints the traceback with the note underneath the error line. Delete the outer try and run it to see.

Why it works

Notes are stored on the exception in a list, __notes__, and the traceback printer shows each one after the message. The exception keeps its type, so anything further up that catches ValueError still does. You can add several notes as the error passes through different layers: the row, then the file, then the job.

When not to use it

If you're going to handle the error right there, handle it, don't annotate it. And before 3.11 add_note doesn't exist; the older way is raise ValueError(f"row {line_no}: ...") from exc, which creates a new error and chains the original. That changes the message, so code matching on it may break. The exceptions lesson covers chaining in full.

Learn it properly: Exceptions: Patterns Beyond the Basics