Back to Blog
Python

Python except as: Clinging to Exception Details

python except as: Learn how the `except ... as ...` clause binds an exception instance in Python, what you can inspect on it, and where the bound variable can and cann...

exception handlingtry exceptPython errorsexception chainingerror handling
An illustration of a Python exception being caught by a handler block that binds it to a named variable for inspection.

python except as requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.

The except ... as ... clause in Python does two things: it marks a block as a handler for a specific exception type, and it binds the raised exception instance to a local name. That second part is what separates it from a bare except: block. Without the as clause, you can only react to the exception's type; with it, you can inspect the instance itself.

try: value = int(user_input) except ValueError as err: print(f"Invalid number: {err}")

Here err is the ValueError instance that was raised. The print call shows its string representation, which is the message that was passed when the exception was constructed.

The Syntax of except as

The general form is:

try: # code that may raise except SomeException as name: # handler that can use name

The name is bound only within the handler block. In Python 3, the binding is deleted when the handler exits, which prevents a subtle memory leak that existed in Python 2. If you need the exception after the handler, you must store it elsewhere:

try: risky_call() except RuntimeError as err: saved = err

The variable err no longer exists after the except block, but saved still references the same instance.

What the Bound Variable Actually Contains

The bound name references the exception instance, not a string. That matters because the instance carries more than its message. Most built-in exceptions expose attributes that are useful in a handler. ValueError has no extra attributes, but OSError does:

try: with open(path) as f: data = f.read() except OSError as err: print(err.errno) print(err.filename) print(err.strerror)

For OSError, errno is the operating system error number, filename is the path that failed, and strerror is the human-readable description. The same pattern applies to other built-in exceptions:

Exception typeAttributeMeaning
OSErrorerrnoOS error number
OSErrorfilenamePath that caused the error
OSErrorstrerrorHuman-readable error text
UnicodeErrorencodingEncoding in use
UnicodeErrorreasonCause of the failure
StopIterationvalueReturn value of the generator

For your own exception classes, any attributes you set in __init__ are available the same way.

Scope and Lifetime of the Exception Variable

In Python 3, the exception variable is cleared when the except block finishes. This is deliberate: the reference cycle that could keep traceback frames alive in Python 2 is broken automatically.

try: raise ValueError("boom") except ValueError as err: pass # err is no longer defined here

Trying to reference err after the handler raises NameError. If you need the instance later, copy it to another name inside the handler. This also means you should not rely on the exception variable being available in a finally block that follows the handler.

Reraising and Exception Chaining

When a handler raises a new exception, Python attaches the original exception as context. You can access it through __context__ on the new exception, and you can control the chain explicitly with raise ... from:

try: parse(data) except ParseError as err: raise ValidationError("input rejected") from err

The from err clause sets __cause__ on the new exception and explicitly links the two. The traceback shows both. Using from None hides the original context, which is useful when the new exception already contains all the diagnostic information and the original traceback would only add noise.

Catching Multiple Exception Types

The as clause works with tuples of exception types:

try: result = lookup(key) except (KeyError, IndexError) as err: log.warning("missing key or index: %s", err)

The bound variable is the actual instance of whichever type was raised, so you can still branch on type(err) inside the handler if the two types need different treatment. The tuple form is preferable to two separate handlers when the recovery logic is identical.

Common Mistakes and Their Consequences

A frequent mistake is catching Exception when the handler only needs a specific type. Catching Exception does not catch KeyboardInterrupt or SystemExit, because those inherit from BaseException, but it does catch programming errors like TypeError and AttributeError that you probably want to surface rather than swallow.

Another mistake is reusing a name that is also used elsewhere in the function. Because the exception variable is deleted at the end of the handler, a later reference to that name will raise NameError instead of silently using a stale value. This is a common source of confusion when a function both handles exceptions and uses the same name for a normal local variable.

A third mistake is raising a new exception without from when the original exception is the actual cause. The new exception will still carry the original as __context__, so the information is not lost, but the traceback is longer and the relationship is less explicit than with raise ... from err.

Runtime Cost and When to Avoid Exception Handling

Exception handling in CPython has a real cost when an exception is actually raised. The try block itself is cheap; the cost appears when the exception is created, because building a traceback involves allocation and frame inspection. That means exceptions should not be used for control flow in tight loops. If a loop frequently raises and catches the same exception, the overhead becomes measurable:

# Avoid this in a hot loop try: value = mapping[key] except KeyError: value = default # Prefer this when the miss rate is high value = mapping.get(key, default)

The dict.get approach avoids constructing an exception entirely. The same principle applies to other containers: check membership or use a default-returning method before falling back to exception handling. When the exceptional case is genuinely rare, the try/except form is fine and often more readable than a defensive check.

python except as: Practical Usage and Code Examples | RYUSLOG DEV