Back to Blog
Python

Python Exception Variable: Syntax and Scope

Learn how to use the Python exception variable in try/except blocks, understand its scope and attributes, and see practical logging and error-handling examples.

Exception Handlingtry-exceptError HandlingPython SyntaxLogging
Illustration of a Python try-except block showing an exception variable being bound and used for logging.

An except clause can bind the raised exception to a name with the as keyword. That name is the exception variable. It lets you inspect the exception object's message, attributes, and traceback, and it is the normal way to get details about an error in Python.

The basic shape is:

try: risky_operation() except ValueError as e: print(f"Caught: {e}")

Here, e refers to the ValueError instance that was raised. You can name it anything, but e, err, and ex are common conventions. The variable is available only inside the except block where it is defined.

The Basic Syntax of the Exception Variable

The exception variable is introduced by the as keyword in the except clause. The full form is:

try: # code that may raise except SomeException as var: # handle exception, using var

If you do not need the exception object, omit as var entirely. This is useful when you only care about the type of exception and not its details. For example:

try: process_data() except TimeoutError: print("Operation timed out")

Omitting the variable keeps the handler concise. However, if you need to log the message or inspect custom attributes, you must bind it.

What the Exception Variable Actually Contains

The exception variable is an instance of the exception class. It carries several useful attributes. The most commonly used is args, a tuple of the arguments passed to the exception constructor. For most built-in exceptions, str(e) returns the human-readable message when there is one argument; with multiple arguments, it typically returns the tuple representation of args.

try: raise ValueError("Invalid value", 42) except ValueError as e: print(e.args) # ('Invalid value', 42) print(str(e)) # ('Invalid value', 42)

Beyond args, the exception object may have custom attributes defined by the exception class. For example, OSError has errno and strerror. The variable also exposes the traceback via __traceback__, though you rarely need to access it directly because the logging module handles it automatically.

Understanding what the variable contains matters when you write generic error handlers that need to extract meaningful information from different exception types.

Scope and Lifetime of the Exception Variable

In Python 3, the exception variable is deleted after the except block finishes. This prevents accidental references to a stale exception and frees the reference earlier.

try: risky() except ValueError as e: print(e) # e is not defined here # print(e) # NameError: name 'e' is not defined

If you need to keep the exception object after the block, assign it to a different name inside the block:

try: risky() except ValueError as e: saved_exc = e print(e) # saved_exc is still available

This scoping behavior encourages you to handle the exception fully within the block. If you find yourself needing the exception later, consider whether you should be re-raising it or wrapping it in a new exception.

Using the Exception Variable for Logging and Error Reporting

The most practical use of the exception variable is to include exception details in logs. The logging module provides logger.exception(), which automatically captures the current exception and its traceback. You can still use the variable to add contextual information.

import logging logger = logging.getLogger(__name__) try: user = fetch_user(user_id) except UserNotFoundError as e: logger.error("User %s could not be found: %s", user_id, e) raise

Here, the exception variable e is used to include the specific error message in the log. The raise statement re-raises the same exception, preserving the original traceback. This pattern is common in layered applications where you want to log at the boundary but still propagate the failure upward.

For custom exceptions, the variable gives access to fields you defined. For example:

class InsufficientFundsError(Exception): def __init__(self, balance, amount): self.balance = balance self.amount = amount super().__init__(f"Insufficient funds: need {amount}, have {balance}") try: withdraw(account, amount) except InsufficientFundsError as e: print(f"Balance: {e.balance}, Requested: {e.amount}")

This is where the exception variable is especially useful: it lets you pass structured data along with the error message, and the handler can use that data to make decisions or produce richer logs.

Exception Chaining and the __cause__ Attribute

When you raise a new exception while handling another, Python automatically sets the __context__ attribute on the new exception. You can also explicitly set __cause__ using the raise ... from syntax. The exception variable of the outer handler can access these attributes to understand the original failure.

try: try: read_config() except OSError as e: raise RuntimeError("Config read failed") from e except RuntimeError as outer: print(outer.__cause__) # the original OSError instance

In the inner block, e is the OSError instance. After re-raising as RuntimeError, the outer handler's variable outer has __cause__ pointing to that same OSError instance. This chain is valuable for debugging because it preserves the full history of failures.

You can also access __context__ for implicit chaining, but __cause__ is more explicit and usually clearer. When you log an exception, the traceback includes both the current exception and its cause, so the exception variable helps you navigate that chain programmatically if needed.

Common Mistakes and Pitfalls

A frequent mistake is trying to use the exception variable outside the except block. Because Python 3 deletes it, this raises a NameError. If you need the exception later, store it explicitly.

Another pitfall is shadowing the exception variable in a nested try block. If you reuse the same name, the inner handler can make the outer exception inaccessible after the inner block.

try: outer_operation() except ValueError as e: try: inner_operation() except ValueError as e: # same name as outer handler print(e) # inner exception # e has been deleted here, so the outer exception is no longer accessible

Use distinct names like outer_err and inner_err to avoid this. Also, be careful when raising a new exception from inside an except block: if you use raise without an argument, it re-raises the current exception. If you use raise NewError(), the original exception becomes the context. Both behaviors are useful, but you must know which one you intend.

Python Version Differences and Compatibility

The behavior of the exception variable differs between Python 2 and Python 3. In Python 2, the variable remained in scope after the except block, which could lead to subtle bugs. Python 3 changed this to delete the variable at the end of the block. Python 2 is end-of-life, but if you maintain code that must support both, do not rely on the variable after the block.

Python 3 also introduced the raise ... from syntax, which is not available in Python 2. This affects how you set __cause__ and how exception chaining is handled. Modern Python 3 code should use the as variable for binding and from when explicitly setting a cause.

Maintainability: When to Use the Exception Variable

Using the exception variable is not always necessary. Overusing it can clutter your code. Use it when you need to:

  • Log the exception message or custom attributes.
  • Make a decision based on the exception details.
  • Re-raise a new exception while preserving the original cause.

If you only need to react to the exception type, omit the variable. This keeps the handler concise and signals that the details are irrelevant. For example:

try: send_notification() except ConnectionError: fallback_to_email()

Here, binding the variable would add noise without value. On the other hand, when you are building a library or a service boundary, the exception variable is essential for propagating context. The key is to match the usage to the information you actually need.

In production code, prefer logger.exception() over print(e) because it includes the full traceback. The exception variable is still useful for adding contextual data to the log message, as shown earlier. This combination gives you both the structured details and the stack trace, making operational debugging much easier.

Remember that the exception variable is just a reference to an object. The traceback is attached to the exception when it is raised. When you re-raise or chain exceptions, the traceback is updated accordingly. Understanding this helps you avoid accidentally truncating the traceback by catching and discarding exceptions without re-raising them.

Ultimately, the exception variable is a simple but powerful tool. Knowing its scope, attributes, and interaction with exception chaining allows you to write error handlers that are both robust and maintainable.

How to Use Python's Exception Variable: Syntax, Scope, and Examples | RYUSLOG DEV