Back to Blog
Python

Python raise from: Chaining Exceptions Clearly

python raise from: Learn how Python's raise from syntax chains exceptions, preserves the original cause in tracebacks, and when to use from None for cleaner error output.

exception chainingtracebackerror handlingpython exceptionsdebugging
Illustration of a Python exception chaining to its root cause with the raise from syntax

When an exception occurs inside an exception handler, Python automatically attaches the original exception as context. The python raise from syntax makes that relationship explicit and controls how the traceback is displayed. It is a small part of the language, but it directly affects how readable your error output is for anyone debugging your code.

What raise ... from ... Does

In Python, raise accepts a second expression after from:

try: result = parse_config(path) except OSError as exc: raise ConfigError(f"could not load config from {path}") from exc

The from exc clause tells Python that the new exception was directly caused by exc. When the traceback is printed, both exceptions appear, with the original shown as "The above exception was the direct cause of the following exception." This makes the chain of failures visible in a single traceback, so the reader can follow the failure from the original I/O error to the configuration error that surfaced to the caller.

Implicit Chaining Without from

If you raise a new exception inside an except block without from, Python still records the original exception, but as context rather than cause:

try: result = parse_config(path) except OSError as exc: raise ConfigError(f"could not load config from {path}")

The traceback shows both exceptions, but the message reads "During handling of the above exception, another exception occurred." The distinction matters: context means the original exception was being handled when the new one was raised; cause means the new exception is a direct consequence of the original.

AspectImplicit contextExplicit from
Syntaxraise NewError()raise NewError() from original
Traceback message"During handling of the above exception...""The above exception was the direct cause..."
RelationshipOriginal was being handledOriginal caused the new exception

When to Use raise ... from ...

Use from when the new exception is a deliberate translation of the original. A library that wraps low-level database errors into a domain-specific exception should chain the original so callers can still inspect the root cause:

class UserRepository: def find(self, user_id): try: row = self.connection.execute( "SELECT * FROM users WHERE id = ?", (user_id,) ) except DatabaseError as exc: raise UserLookupError(f"user {user_id} not found") from exc

The caller sees the domain error, but the traceback still contains the database error that caused it. That traceability is the main reason to use from. Without it, the causal link is implicit and the traceback wording is less precise.

Suppressing the Context with from None

Sometimes the original exception is noise. If you are converting an exception into a more meaningful one and the original details are irrelevant to the caller, from None suppresses the context entirely:

def load_settings(): try: with open("settings.json") as f: return json.load(f) except (OSError, json.JSONDecodeError): raise SettingsError("settings file is missing or invalid") from None

The traceback shows only SettingsError. This is appropriate when the original exception adds no diagnostic value, such as when the new message already describes the failure completely. Overusing from None can hide useful debugging information, so apply it only when the original exception genuinely does not help.

Reading the Traceback After Chaining

Chained exceptions change how the traceback is read. With from, the traceback shows the original exception first, then the new one. When debugging, you read from the bottom up: the final exception is the one that propagated, and the exceptions above it are the causes. This ordering is consistent whether the chain is implicit or explicit, but explicit chaining makes the causal relationship unambiguous, which matters when the same exception type appears at multiple layers of the stack.

Common Mistakes

A frequent mistake is raising from a value that is not an exception instance. The from clause accepts an exception instance, not a class:

try: ... except ValueError as exc: raise RuntimeError("bad value") from ValueError # raises TypeError

This raises a TypeError because ValueError is a class, not an instance. Use the caught instance instead:

try: ... except ValueError as exc: raise RuntimeError("bad value") from exc

Another mistake is chaining exceptions that have no causal relationship. If you catch an exception and raise an unrelated one, from implies a cause that does not exist. In that case, omit from and let Python record context, or use from None if you want to hide it.

Maintainability Considerations

Chained exceptions are a debugging aid, not a feature to apply mechanically. Every from clause adds traceback length. In a large codebase, deeply nested chains can make logs harder to scan. Keep the chain shallow: translate exceptions once at the boundary where they cross abstraction layers, and let the original exception surface naturally.

from None is a maintenance risk if the code evolves. A later change might add a case where the original exception becomes important, and the suppression hides it. Prefer explicit from over from None unless you are certain the original will never matter. When reviewing code, ask whether the chained exception helps the next developer understand the failure, not whether it makes the current traceback shorter.

python raise from: Practical Usage and Code Examples | RYUSLOG DEV