Using “raise from None” in Python

Klaas van Schelven
Klaas van Schelven; December 27 - 6 min read
An eraser removing some complexity from a stacktrace

In Python, exceptions can be chained to provide context about what went wrong and why. This is achieved through the raise [Exception] from [cause] construct. While this mechanism is often helpful, it can sometimes obscure more than it clarifies. Enter raise Exception from None: a way to suppress exception chaining and simplify stacktraces for better readability.

TL;DR:

  • Simplify debugging: from None removes unnecessary context, making stacktraces easier to read.
  • Direct causation: Use it when the cause and effect are so closely linked they’re effectively the same.
  • Control flow: Particularly useful when exceptions are part of normal program flow or the “ask for forgiveness” pattern.

Default Exception Chaining

By default, Python automatically chains exceptions when one is raised during the handling of another. For example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
from settings_in_db import Setting

class ConfigurationError(Exception):
    pass

def get_setting(name):
    try:
        setting = Setting.objects.get(name=name)
        return setting.value
    except Setting.DoesNotExist as e:
        raise ConfigurationError(f"Setting '{name}' not found")

get_setting("feature_flag")

This results in a stacktrace that shows both exceptions, along with their relationship:

Traceback (most recent call last):
  File "/tmp/example1.py", line 8, in get_setting
    setting = Setting.objects.get(name=name)
  File "/tmp/settings_in_db/__init__.py", line 7, in get
    raise Setting.DoesNotExist(f"Setting '{name}' not found")
settings_in_db.Setting.DoesNotExist: Setting 'feature_flag' not found

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/tmp/example1.py", line 13, in <module>
    get_setting("feature_flag")
  File "/tmp/example1.py", line 11, in get_setting
    raise ConfigurationError(f"Setting '{name}' not found")
__main__.ConfigurationError: Setting 'feature_flag' not found

What’s going on here? Let’s break it down:

  1. Something goes wrong when trying to get a setting from a database. This is shown in the stacktrace as the first exception: a DoesNotExist exception is raised when the setting is not found.

  2. During the handling of this exception, i.e. in a except block that wraps the database call, another exception is raised: a ConfigurationError exception.

Note that both exceptions are shown in the stacktrace: first the DoesNotExist, then the ConfigurationError.

In the stacktrace above, the wording of “During handling… another exception occurred” suggests that the relationship between the two exceptions is almost accidental. If we look at the actual code, it seems that the re-raising is in fact quite intentional, since a database error is caught explicitly and then re-raised as a configuration error.

We can make this more clear by explicitly declaring the second exception to be a direct consequence of the first by using the from e syntax:

# other code as before; omitted for brevity
raise ConfigurationError(f"Setting '{name}' not found") from e

This changes the stacktrace to make the relationship between the exceptions more explicit:

[..]
The above exception was the direct cause of the following exception:
[..]

Showing both exceptions, and their relationship, can often be helpful for debugging (more about this later). However, it also comes with some downsides, which we’ll explore next.

Problems with Chained Exceptions

The basic problem with exception chaining is that it can make stacktraces more complex and harder to follow. First of all, there is simply more information to display, and thus more information to parse.

Secondly, there is a more subtle problem: the chain of exceptions can make it harder to see the flow of the code. I wrote about this in a previous article, but the gist of it is this:

The stacktrace is no longer a linear representation of the code execution; a single stacktrace can be read from to top bottom, whereby the top is the first thing that happened, and the bottom is the last thing that happened. With chained exceptions, this is no longer the case, because the stacktrace is split into multiple parts, where each part represents a different exception. On top of that, Python’s default representation of chained exceptions prunes some of the context, which makes it even harder to follow the flow of the code.

In the example above, the order in which things happened is as follows:

  • File "/tmp/example1.py", line 13, in <module>
  • File "/tmp/example1.py", line 8, in get_setting
  • File "/tmp/settings_in_db/__init__.py", line 7, in get
  • File "/tmp/example1.py", line 11, in get_setting

Note how mapping this order to the stacktrace is not straightforward: the actual order of execution starts with the first part of the second exception, then goes into the whole of the first exception, only to return to the second exception.

Suppressing Exception Chaining

Before considering when to suppress exception chaining, let’s first look at how to do it.

This is quite simple: when raising an exception, use the from None syntax to suppress the chaining of exceptions. (The formal specification is in the PEP). Here’s what it looks like:

try:
    # code that raises an exception
except SomeException as e:
    raise AnotherException("Something went wrong") from None

Using this syntax, the stacktrace will only show the final exception that was raised, and not the exception that was caught. This can make the stacktrace much simpler and easier to follow:

Traceback (most recent call last):
  File "/tmp/example1.py", line 13, in <module>
    get_setting("feature_flag")
  File "/tmp/example1.py", line 11, in get_setting
    raise ConfigurationError(f"Setting '{name}' not found") from None
__main__.ConfigurationError: Setting 'feature_flag' not found

As you can see, the stacktrace is much simpler: just a single Exception is shown, and the exception can be read from top to bottom or vice versa without losing track of the order of execution.

When to Suppress Exception Chaining

Now that we know how to suppress exception chaining, let’s explore when to use from None.

Consider, for example, the following code snippet:

class AttrLikeDict:
    """Dict that behaves like an object (.attribute lookup)."""

    def __init__(self, d):
        self.d = d

    def __getattr__(self, name):
        try:
            return self.d[name]
        except KeyError:
            raise AttributeError(f"Attribute '{name}' not found")

settings = AttrLikeDict({"feature_flag": "enabled"})
print(settings.faeture_flag)

This code snippet demonstrates a small utility class that allows object-like access to an internal dictionary. A common use case for such a class is to wrap configuration settings.

However, there’s a typo in the last line (the actual usage of the class): faeture_flag should be feature_flag. This results in the following stacktrace:

Traceback (most recent call last):
  File "/tmp/example2.py", line 9, in __getattr__
    return self.d[name]
KeyError: 'faeture_flag'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/tmp/example2.py", line 14, in <module>
    print(settings.faeture_flag)
  File "/tmp/example2.py", line 11, in __getattr__
    raise AttributeError(f"Attribute '{name}' not found")
AttributeError: Attribute 'faeture_flag' not found

This highlights the same problems as before: the stacktrace is more complex and harder to follow.

In fact, in this case, it’s even worse: because the two exceptions are so similar, it’s even harder to see what’s going on: The similarities make it harder to see the differences, and thereby to mentally keep track of what’s what:

The similarity between the exception also reduces the actual value of showing them both: The KeyError is not providing any information that the AttributeError is not providing, because the one is just a thin wrapper for the other.

This is a case where the default chaining behavior is not helpful, and where suppressing the chaining of exceptions would be beneficial. Using from None, our stacktrace comes out like this:

Traceback (most recent call last):
  File "/tmp/example3.py", line 14, in <module>
    print(settings.faeture_flag)
  File "/tmp/example3.py", line 11, in __getattr__
    raise AttributeError(f"Attribute '{name}' not found") from None
AttributeError: Attribute 'faeture_flag' not found

This is much more clear: the KeyError is gone, and the stacktrace is much simpler. There’s also no loss of information here: the AttributeError provides all the context that the KeyError provides.

Generalizing from the example

The general rule of thumb is that you should suppress exception chaining when the cause and effect of the exceptions are quite closely linked, and when the context provided by the exceptions is redundant.

This general principle applies in particular to exceptions that are used for control flow, or that are part of the “ask for forgiveness” pattern. The dictionary lookup in the above example is a good example of this: the KeyError is expected in the sense that we always deal with it in the same way. Another way of saying this is that cause and effect are so closely linked that they are effectively the same, i.e. a KeyError on the dictionary lookup is effectively the same as an AttributeError on the attribute lookup, because the whole point of the AttrLikeDict class is to provide attribute-like access to a dictionary.

When to Chain Exceptions

On the flip side, chaining exceptions is essential when both the cause and effect contribute valuable information. For example, when dealing with database errors, the underlying exception provides technical details, while the subsequent exception explains the context of the failure:

def query_database():
    raise ConnectionError("Database unavailable")

try:
    query_database()
except ConnectionError as e:
    raise RuntimeError("Failed to fetch user data") from e

The resulting stacktrace:

Traceback (most recent call last):
  File "example.py", line 2, in query_database
    raise ConnectionError("Database unavailable")
ConnectionError: Database unavailable

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "example.py", line 6, in <module>
    raise RuntimeError("Failed to fetch user data") from e
RuntimeError: Failed to fetch user data

Here, both exceptions provide critical information the database is unavailable, and this unavailability caused the failure to fetch user data. That is: one issue relates to the technical details of the database, while the other relates to the consquences of this issue to your application-level logic.

How to fix this issue, e.g. wether to retry the operation or to inform the user, depends on the application context. Which is exactly why it’s important to keep both exceptions in the stacktrace.

In this case, we still need to deal with “the stacktrace puzzle” that I mentioned earlier, but the value of the context provided by the exceptions outweighs the downsides of the complexity.

“from None” does not mean “No Cause”

The from None syntax can feel a bit counterintuitive. It might suggest there is no root cause, but that’s not the case. The root cause still exists – it’s just not shown in the stacktrace.

Let’s clarify the counterintuitiveness by considering the three ways exceptions can relate to one another, ordered from least to most direct connection:

  1. Unrelated Exception: An exception happens independently during the handling of another. Here, the developer would not use the from syntax at all, leading to a “during handling” message in the stacktrace.

  2. Direct Cause: An exception is caused by another, where the relationship is meaningful and relevant to debugging. In this case, the developer should use from e to make the relationship explicit (“direct cause”).

  3. Extremely Direct Cause: An exception is so directly caused by another that they’re effectively the same error. Here, the developer should use from None to simplify the stacktrace and avoid redundant information.

Going through this list from top to bottom, one notices that in the step from 1 to 2, an explicit relationship between 2 things is added in the code, and in the step from 2 to 3 it is removed, despite the fact that the relationship is even more direct.

This seeminging unintuitive change in the code is a direct consequence of what we want to achieve in our stacktraces: when a relationship between 2 exceptions is quite close, we want to express that using the “direct cause” phrasing, but when it’s so close that the exceptions are effectively the same, the redundant one is better left hidden.

In other words: “from None” does not mean “no cause”. It means “no cause worth showing”.

Conclusion

The raise ... from None construct is a simple yet powerful tool for managing exception clarity in Python. Use it when:

  • The cause and effect of exceptions are closely linked, making the context redundant.
  • Exceptions are used for control flow, and the chain adds unnecessary mental overhead.

When both exceptions provide valuable information – such as with database errors or external system failures – explicitly chaining them with raise ... from ensures you have the full picture. By understanding when and how to use from None, you can write code that’s easier to debug and maintain.

Now that you’re here: Bugsink is a tool that helps you track and manage exceptions in your applications (including Python). It provides a clear overview of all your errors, and helps you fix them faster.