Re-raising and exception chaining

Re-raising: handle something, but let the failure continue

Sometimes you want to react to an exception — log it, clean something up — without actually treating it as handled. A bare raise, with no argument, inside an except block re-raises the exact exception that was just caught, continuing on exactly as if that except block wasn’t there at all:

Try it — edit and run

The log line prints, and the original ValueError still propagates afterward, uncaught, exactly as it would have without the try block at all — run_with_logging observed the failure without hiding it from whatever called it.

Wrapping in a new exception: the implicit chain

Instead of re-raising the same exception, you can raise a different one — commonly, a custom exception from the previous section, to translate a low-level failure into something more meaningful for whoever’s calling your code:

Try it — edit and run

Notice Python shows both tracebacks — the original ValueError and the new ConfigError — connected by “During handling of the above exception, another exception occurred.” This happens automatically any time you raise a new exception from inside an except block; Python remembers what was being handled and shows it for context, even though you never asked it to.

Making the connection explicit: raise ... from

The automatic chaining above is useful, but it can also happen by accident — any exception raised inside an except block gets chained this way, whether or not the two are actually related. raise NewError(...) from original makes the relationship deliberate and explicit instead, and produces a clearer message specifically framing it as a cause:

Try it — edit and run

“The above exception was the direct cause of the following exception” — compare that to the plain “During handling of…” from before: from e tells both Python and, more importantly, the next person reading this traceback that the ConfigError isn’t incidental, it’s a deliberate translation of that specific ValueError. The debugging value is real: the original low-level cause (a bad string) is preserved right alongside the higher-level, more meaningful error your code actually raised.

Catching more than one exception type at once

Sometimes several different exception types should be handled the same way — you’ve already seen every type get its own separate except clause; a tuple lets one clause catch several types identically:

Try it — edit and run

except (json.JSONDecodeError, TypeError): catches either type with one block — json.loads(None) raises TypeError rather than JSONDecodeError, but here both are treated identically, so listing them in one tuple avoids writing the same return {} twice under two separate except clauses.

Check your understanding
1/5

What does a bare raise, with no arguments, do inside an except block?

Exercise · Graded

Open and parse the file at path. Catch FileNotFoundError and json.JSONDecodeError separately (not in one tuple, since each needs its own message), and in each case raise ConfigError with the specified message, chained with 'from' to the original exception.